[gnome-shell] Move calendar handling out-of-process



commit e9e30138bd617a410a60b45ac0a5576094ed63d4
Author: David Zeuthen <davidz redhat com>
Date:   Fri Feb 25 17:42:25 2011 -0500

    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 <davidz redhat com>

 configure.ac                                       |   18 +-
 js/ui/calendar.js                                  |  245 ++--
 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 --
 src/{calendar-client => calendar-server}/README    |    0
 .../calendar-debug.h                               |    0
 .../calendar-sources.c                             |    0
 .../calendar-sources.h                             |    0
 src/calendar-server/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, 1359 insertions(+), 2790 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index c3dc639..8155bae 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 643a66a..abedb26 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() {
+const CalendarServerIface = {
+    name: 'org.gnome.Shell.CalendarServer',
+    methods: [{ name: 'GetEvents',
+                inSignature: 'xxb',
+                outSignature: 'a(sssbxxa{sv})' }],
+    signals: [{ name: 'Changed',
+                inSignature: '' }]
+};
+
+const CalendarServer = function () {
     this._init();
-}
+};
 
-EvolutionEventSource.prototype = {
-    _init: function() {
-        this._native = new Shell.EvolutionEventSource();
-        this._native.connect('changed', Lang.bind(this, function() {
-            this.emit('changed');
-        }));
-    },
+CalendarServer.prototype = {
+     _init: function() {
+         DBus.session.proxifyObject(this, 'org.gnome.Shell.CalendarServer', '/org/gnome/Shell/CalendarServer');
+     }
+};
 
-    requestRange: function(begin, end) {
-        this._native.request_range(begin.getTime(), end.getTime());
-    },
+DBus.proxifyPrototype(CalendarServer.prototype, CalendarServerIface);
 
-    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;
-    },
+// an implementation that reads data from a session bus service
+function DBusEventSource(owner) {
+    this._init(owner);
+}
 
-    hasEvents: function(day) {
-        let dayBegin = _getBeginningOfDay(day);
-        let dayEnd = _getEndOfDay(day);
+function _datesEqual(a, b) {
+    if (a < b)
+        return false;
+    else if (a > b)
+        return false;
+    return true;
+}
 
-        let events = this.getEvents(dayBegin, dayEnd);
+function _dateIntervalsOverlap(a0, a1, b0, b1)
+{
+    if (a1 <= b0)
+        return false;
+    else if (b1 <= a0)
+        return false;
+    else
+        return true;
+}
 
-        if (events.length == 0)
-            return false;
 
-        return true;
-    }
-};
-Signals.addSignalMethods(EvolutionEventSource.prototype);
+DBusEventSource.prototype = {
+    _init: function(owner) {
+        this._resetCache();
 
-// Finally, an implementation with fake events
-function FakeEventSource() {
-    this._init();
-}
+        this._dbusProxy = new CalendarServer(owner);
+        this._dbusProxy.connect('Changed', Lang.bind(this, this._onChanged));
 
-FakeEventSource.prototype = {
-    _init: function() {
+        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));
+    },
 
-        this._fakeEvents = [];
+    _resetCache: function() {
+        this._events = [];
+        this._lastRequestBegin = null;
+        this._lastRequestEnd = null;
+    },
 
-        // 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));
-        }
+    _onNameAppeared: function(owner) {
+        this._resetCache();
+        this._loadEvents(true);
+    },
 
-        // '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));
-        }
+    _onNameVanished: function(oldOwner) {
+        this._resetCache();
+        this.emit('changed');
+    },
 
-        // '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));
-        }
+    _onChanged: function() {
+        this._loadEvents(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));
+    _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();
+            });
         }
 
-        // '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;
+        this._events = newEvents;
+        this.emit('changed');
     },
 
-    _updateTransientEvent: function() {
-        this._includeTransientEvent = !this._includeTransientEvent;
-        this._transientEventCounter = this._transientEventCounter + 1;
-        this._transientEvent.summary = 'A Transient Event (' + this._transientEventCounter + ')';
-        this.emit('changed');
-        Mainloop.timeout_add(5000, Lang.bind(this, this._updateTransientEvent));
+    _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) {
+    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 1f193aa..6dee534 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 5c6b952..873980d 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-server.am b/src/Makefile-calendar-server.am
new file mode 100644
index 0000000..e617668
--- /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 ea12a2b..71cb018 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/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 0000000..4d4d3a5
--- /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 <davidz redhat com>
+ *
+ * Based on code from gnome-panel's clock-applet, file calendar-client.c, with Authors:
+ *
+ *     Mark McLoughlin  <mark skynet ie>
+ *     William Jon McCann  <mccann jhu edu>
+ *     Martin Grimme  <martin pycage de>
+ *     Christian Kellner  <gicmo xatom net>
+ *
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <gio/gio.h>
+
+#define HANDLE_LIBICAL_MEMORY
+#include <libecal/e-cal.h>
+#include <libecal/e-cal-time-util.h>
+#include <libecal/e-cal-recur.h>
+#include <libecal/e-cal-system-timezone.h>
+
+#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[] =
+  "<node>"
+  "  <interface name='org.gnome.Shell.CalendarServer'>"
+  "    <method name='GetEvents'>"
+  "      <arg type='x' name='since' direction='in'/>"
+  "      <arg type='x' name='until' direction='in'/>"
+  "      <arg type='b' name='force_reload' direction='in'/>"
+  "      <arg type='a(sssbxxa{sv})' name='events' direction='out'/>"
+  "    </method>"
+  "    <signal name='Changed'/>"
+  "    <property name='Since' type='x' access='read'/>"
+  "    <property name='Until' type='x' access='read'/>"
+  "  </interface>"
+  "</node>";
+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 0000000..5addce6
--- /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 dd5e967..e748ac6 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-global.c b/src/shell-global.c
index f42237e..4f08bca 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 0adb32c..66b9127 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__ */



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