
Wie in dem Artikel CalDAV – Der Weg zum Kalender angekündigt, haben wir ein Modul zum Austausch von Kalenderdaten zwischen einem externen Kalender und einer conzept 16-Applikation entwickelt. Dieses Modul setzt auf das CalDAV-Protokoll auf und bietet alle gängigen Funktionen eines CalDAV-Clients (Termine abholen, anlegen, bearbeiten und löschen). In diesem Artikel möchte ich Ihnen die Funktionsweise und Implementierung dieser Client-Prozedur vorstellen.
Funktionsweise
Die Kommunikation findet zwischen Client und Server via HTTP statt. Der Client schickt dabei eine Anfrage an den Server, der auf diese wiederum antwortet. Eine typische Anfrage wäre das Abholen von Terminen. Termine liegen als .ics-Datei auf dem Server vor. Eine .ics-Datei enthält Kalenderdaten im iCalendar-Format. Einige Server stellen diese Dateien bereit, unterstützen das CalDAV-Protokoll aber nicht. Dies ist meist bei statischen Kalendern (z.B. einem Feiertagskalender) so. Diese Kalender besitzen meist eine .ics-Datei, die alle Termine enthält. Zum Vergleich: Ein Server, der das CalDAV-Protokoll unterstützt, besitzt für jeden Termin eine eigene .ics-Datei, welche einzeln abgeholt werden kann.
Eine einfache Anfrage um einen Kalender oder einen einzelnen Termin abzuholen sieht wie folgt aus:
GET /calendar/event.ics HTTP/1.1
Sofern die Datei existiert, antwortet der Server mit dem HTTP-Statuscode 200 OK und enthält im Body den Termin im iCalendar-Format:
BEGIN:VCALENDAR
PRODID:-//vectorsoft//conzept 16 CalDAV-Server//DE
VERSION:2.0
BEGIN:VEVENT
SUMMARY:Essen bei Luigi
LOCATION:Pizzeria Luigi
DESCRIPTION:Neue Pizza ausprobieren!
DTSTART;TZID=Europe/Berlin:20130503T180000
DTEND;TZID=Europe/Berlin:20130503T210000
DTSTAMP:20130425T144817Z
UID:bjj3pbehp5ovjf74vent9a3820
CREATED:20130503T140000Z
LAST-MODIFIED:20130503T140000Z
SEQUENCE:0
END:VEVENT
END:VCALENDAR
Das iCalendar-Format wird im RFC 5545 beschrieben. Eine kurze Einführung gibt es auch auf Wikipedia (englisch).
Das Anlegen und Verändern von Terminen wird über die HTTP-Methode PUT ermöglicht, das Löschen über die HTTP-Methode DELETE.
Das Abholen von Terminen mittels HTTP-Methode GET mag für statische Kalender ausreichen, aber spätestens wenn Termine verändert werden sollen, sind die Methoden des WebDAV-Protokolls geeigneter.
Sofern der Server die Methoden des WebDAV-Protokolls unterstützt, fordert der Client die Termine per REPORT-Methode an. Bei einer REPORT-Anfrage können Filter gesetzt werden, so dass nur bestimmte Termine zurückgeliefert werden. Weiterhin kann festgelegt werden, welche Eigenschaften zu der Ressource, in diesem Fall der Termin, zurückgeliefert werden sollen. Die typische Anfrage des Clients sieht wie folgt aus:
REPORT /calendar/ HTTP/1.1
Depth: 1
Content-Type: text/xml; charset=utf-8
Content-Length: xxx
<?xml version="1.0"?>
<B:calendar-query xmlns:B="urn:ietf:params:xml:ns:caldav">
<A:prop xmlns:A="DAV:">
<A:getetag/>
</A:prop>
<B:filter>
<B:comp-filter name="VCALENDAR">
<B:comp-filter name="VEVENT">
<B:time-range start="20130101T000000Z"/>
</B:comp-filter>
</B:comp-filter>
</B:filter>
</B:calendar-query>
Die Antwort enthält die Adressen (href) aller Termine, die nach dem 01.01.2013 stattfinden, und dessen ETag.
HTTP/1.1 207 Multi-Status
Content-Type: text/xml; charset=utf-8
Content-Length: xxx
<?xml version="1.0"?>
<A:multistatus xmlns:A="DAV:">
<A:response>
<A:href>/calendar/event-1.ics</A:href>
<A:propstat>
<A:status>HTTP/1.1 200 OK</A:status>
<A:prop>
<d:getetag>"1-5"</A:getetag>
</A:prop>
</A:propstat>
</A:response>
<A:response>
<A:href>/calendar/event-2.ics</A:href>
<A:propstat>
<A:status>HTTP/1.1 200 OK</A:status>
<A:prop>
<A:getetag>"2-5"</A:getetag>
</A:prop>
</A:propstat>
</A:response>
</A:multistatus>
Das ETag einer Ressource verändert sich, wenn sich die Ressource verändert. Dadurch kann der Client feststellen, ob sich ein Termin verändert hat.
Im nächsten Schritt fordert der Client alle Termine an, die er nicht kennt oder die sich verändert haben. Theoretisch könnte jeder Termin mit einer GET-Anfrage abgeholt werden. Praktisch können dadurch aber sehr viele GET-Anfragen entstehen. Daher wird ein sogenannter multiget, eine Variante der REPORT-Methode verwendet:
REPORT /calendar/ HTTP/1.1
Depth: 1
Content-Type: text/xml; charset=utf-8
Content-Length: xxx
<?xml version="1.0"?>
<B:calendar-multiget xmlns:B="urn:ietf:params:xml:ns:caldav">
<A:prop xmlns:A="DAV:">
<B:calendar-data/>
</A:prop>
<A:href xmlns:A="DAV:">/calendar/event-1.ics</A:href>
<A:href xmlns:A="DAV:">/calendar/event-2.ics</A:href>
</B:calendar-multiget>
Dadurch können alle neuen und veränderten Termine in zwei Abfragen abgeholt werden. Dass ein Termin gelöscht wurde, erkennt der Client daran, dass der Termin im ersten REPORT nicht mehr mit aufgeführt wird.
Implementierung
Zum Verwenden der Schnittstelle wird die sogenannte Kalenderadresse benötigt. Dies ist die Adresse (URL) unter der ein oder mehrere Kalender zu finden sind.
Private Kalender, wie beispielsweise der Google Calendar, benötigen zusätzlich zur Kalender-URL eine Authentifizierung, bestehend aus Benutzername und Passwort.
Im folgenden Beispiel wird ein öffentlicher Feiertagskalender von ecoline verwendet. Er benötigt keine Authentifizierung.
Beispiel
// Startzeit: Heute Endzeit: Ende des Jahres
tStartTime->vmServerTime();
tEndTime->vpMonth # 12;
tEndTime->vpDay # 31;
tEndTime->vpYear # tStartTime->vpYear;
tEndTime->vpTime # 23:59:59.99;
// Initialisieren: Kalenderadresse, Benutzername, Passwort
tCalDAV # SysCalDAV:Init('http://www.ecoline-service.de/ics/deutschland.ics');
if (tCalDAV > 0)
{
// Kalender auswählen
for tCal # tCalDAV->SysCalDAV:CalRead(sCal.First);
loop tCal # tCalDAV->SysCalDAV:CalRead(sCal.Next, tCal);
while (tCal > 0)
{
// Name des Kalenders merken
tCalName # tCalDAV->SysCalDAV:CalNameGet(tCal);
// Termine des Kalenders aktualisieren/abholen
tCalDAV->SysCalDAV:CalRefresh(tCal);
// Alle Termine des Kalenders durchlaufen
for tEvt # tCalDAV->SysCalDAV:EvtRead(tCal, sCalEvt.First);
loop tEvt # tCalDAV->SysCalDAV:EvtRead(tCal, sCalEvt.Next, tEvt);
while (tEvt > 0)
{
// Anfangszeitpunkt des Termins ermitteln
tEvtTime # tCalDAV->SysCalDAV:EvtDTStartGet(tEvt);
// Alle Termine, die zwischen heute und Ende des Jahres anfangen, ausgeben
if (tEvtTime > tStartTime and tEvtTime < tEndTime)
WinDialogBox(0, tCalName, tCalDAV->SysCalDAV:EvtSummaryGet(tEvt), 0,0,0);
}
}
tCalDAV->SysCalDAV:Term();
}
Die Schnittstelle wurde mit .ics-Dateien und folgenden Servern getestet:
- Google Calendar
- fruux
- Memotoo
- conzept 16 CalDAV-Server