Building a CalDAV client


https://code.google.com/p/sabredav/wiki/BuildingACalDAVClient

Building a CalDAV client

Introduction

This document is a general howto on how to build a CalDAV client. The document is language-agnostic, and considering the massive scope of CalDAV, not complete.

General synchronization concerns

The primary formats for tranfering information is iCalendar for calendar objects (events and tasks) and xml for most other data.

CalDAV is based on WebDAV, which itself is an extension to HTTP.

Some operations will be very familiar if you already have experience with HTTP services (GET, PUT and DELETE), but many are added too (PROPFIND, PROPPATCH, REPORT, MKCOL, MKCALENDAR, ACL).

Most HTTP clients should just support methods they don't know about, so it's wise to simply use a stock HTTP client (or better yet, a DAV or CalDAV client).

One thing in which CalDAV differs from some other synchronization models, is that the 'truth' is always on the server. There should in general never really be a situation where there are conflicts, as the server is always correct.

One implication is that there should not be a separate synchronization step, ideally changes are submitted by the client the moment the user makes modifications.

You should also be prepared to store the entire iCalendar object as-is on the client. Mapping iCalendar properties to an internal data model will often be desired, but I would recommend to keep the actual objects as well.

Clients may add non-standard properties to iCalendar objects. It is important that when you GET and later on PUT an updated iCalendar object, any non-standard properties you may not have built-in support for gets retained.

Syncing a calendar

Simple clients tend to just access CalDAV servers based on the follow 3 setting:

  • username
  • password
  • calendar url

An example of this is Thunderbird Lightning. So this is where we start.

Retrieving calendar information

This is the recommended way to do an initial sync with SabreDAV. Every calendar has a so-called 'ctag'. This ctag works like a change id. Every time the ctag has changed, you know something in the calendar has changed too.

An example request to get the ctag:

PROPFIND /calendars/johndoe/home/ HTTP/1.1
Depth: 0
Prefer: return-minimal
Content-Type: application/xml; charset=utf-8

<d:propfind xmlns:d="DAV:" xmlns:cs="http://calendarserver.org/ns/">
  <d:prop>
     <d:displayname />
     <cs:getctag />
  </d:prop>
</d:propfind>

The PROPFIND request is a http request, defined by WebDAV. PROPFIND allows the client to fetch properties from a url.

CalDAV uses many properties like this, but in this case we just fetch the 'displayname', which is the human-readable name the user gave the calendar, and the ctag. The ctag must be stored for subsequent requests.

The request will return something like:

HTTP/1.1 207 Multi-status
Content-Type: application/xml; charset=utf-8

<d:multistatus xmlns:d="DAV:" xmlns:cs="http://calendarserver.org/ns/">
    <d:response>
        <d:href>/calendars/johndoe/home/</d:href>
        <d:propstat>
            <d:prop>
                <d:displayname>Home calendar</d:displayname>
                <cs:getctag>3145</cs:getctag>
            </d:prop>
            <d:status>HTTP/1.1 200 OK</d:status>
        </d:propstat>
    </d:response>
</d:multistatus>

This multistatus response is very common for Cal and WebDAV. Many requests return an xml document in this exact format, so it is worthwhile writing a standard parser.

The response gives us back the user, the values for the 2 properties and the status.

If the user did not have access to these properties, it's also possible that you get a response like this back:

HTTP/1.1 207 Multi-status
Content-Type: application/xml; charset=utf-8

<d:multistatus xmlns:d="DAV:" xmlns:cs="http://calendarserver.org/ns/">
    <d:response>
        <d:href>/calendars/johndoe/home/</d:href>
        <d:propstat>
            <d:prop>
                <d:displayname />
                <cs:getctag />
            </d:prop>
            <d:status>HTTP/1.1 403 Forbidden</d:status>
        </d:propstat>
    </d:response>
</d:multistatus>

So it is important that when you parse the response, you make sure that the status for the properties was actually 200 OK.

Downloading objects

Now we download every single object in this calendar. To do this, we use a REPORT method.

REPORT /calendars/johndoe/home/ HTTP/1.1
Depth: 1
Prefer: return-minimal
Content-Type: application/xml; charset=utf-8

<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
    <d:prop>
        <d:getetag />
        <c:calendar-data />
    </d:prop>
    <c:filter>
        <c:comp-filter name="VCALENDAR" />
    </c:filter>
</c:calendar-query>

This request will give us every object that's a VCALENDAR object, and its etag.

If you're only interested in VTODO (because you're writing a todo app) you can also filter for just those:

REPORT /calendars/johndoe/home/ HTTP/1.1
Depth: 1
Prefer: return-minimal
Content-Type: application/xml; charset=utf-8

<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
    <d:prop>
        <d:getetag />
        <c:calendar-data />
    </d:prop>
    <c:filter>
        <c:comp-filter name="VCALENDAR">
            <c:comp-filter name="VTODO" />
        </c:comp-filter>
    </c:filter>
</c:calendar-query>

Similarly it's also possible to filter to just events, or only get events within a specific time-range.

This report will return a multi-status object again:

HTTP/1.1 207 Multi-status
Content-Type: application/xml; charset=utf-8

<d:multistatus xmlns:d="DAV:" xmlns:cs="http://calendarserver.org/ns/">
    <d:response>
        <d:href>/calendars/johndoe/home/132456762153245.ics</d:href>
        <d:propstat>
            <d:prop>
                <d:getetag>"2134-314"</d:getetag>
                <c:calendar-data>BEGIN:VCALENDAR
                    VERSION:2.0
                    CALSCALE:GREGORIAN
                    BEGIN:VTODO
                    UID:132456762153245
                    SUMMARY:Do the dishes
                    DUE:20121028T115600Z
                    END:VTODO
                    END:VCALENDAR
                </c:calendar-data>
            </d:prop>
            <d:status>HTTP/1.1 200 OK</d:status>
        </d:propstat>
    </d:response>
    <d:response>
        <d:href>/calendars/johndoe/home/132456-34365.ics</d:href>
        <d:propstat>
            <d:prop>
                <d:getetag>"5467-323"</d:getetag>
                <c:calendar-data>BEGIN:VCALENDAR
                    VERSION:2.0
                    CALSCALE:GREGORIAN
                    BEGIN:VEVENT
                    UID:132456-34365
                    SUMMARY:Weekly meeting
                    DTSTART:20120101T120000
                    DURATION:PT1H
                    RRULE:FREQ=WEEKLY
                    END:VEVENT
                    END:VCALENDAR
                </c:calendar-data>
            </d:prop>
            <d:status>HTTP/1.1 200 OK</d:status>
        </d:propstat>
    </d:response>
</d:multistatus>

This calendar only contained 2 objects. A todo and a weekly event.

So after you retrieved and processed these, for each object you must retain:

  • The calendar data itself
  • The url
  • The etag

In this case all urls ended with .ics. This is often the case, buy you must not rely on this. In this case the UID in the calendar object was also identical to a part of the url. This too is often the case, but again not something you can rely on, so don't make any assumptions.

Finding out if anything changed

To see if anything in a calendar changed, we simply request the ctag again on the calendar. If the ctag did not change, you still have the latest copy.

If it did change, you must request all the etags in the entire calendar again:

REPORT /calendars/johndoe/home/ HTTP/1.1
Depth: 1
Prefer: return-minimal
Content-Type: application/xml; charset=utf-8

<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
    <d:prop>
        <d:getetag />
    </d:prop>
    <c:filter>
        <c:comp-filter name="VCALENDAR">
            <c:comp-filter name="VTODO" />
        </c:comp-filter>
    </c:filter>
</c:calendar-query>

Note that this last request is extremely similar to a previous one, but we are only asking fo the etag, not the calendar-data.

The reason for this, is that calendars can be rather huge. It will save a TON of bandwidth to only check the etag first.

HTTP/1.1 207 Multi-status
Content-Type: application/xml; charset=utf-8

<d:multistatus xmlns:d="DAV:" xmlns:cs="http://calendarserver.org/ns/">
    <d:response>
        <d:href>/calendars/johndoe/home/132456762153245.ics</d:href>
        <d:propstat>
            <d:prop>
                <d:getetag>"2134-314"</d:getetag>
            </d:prop>
            <d:status>HTTP/1.1 200 OK</d:status>
        </d:propstat>
    </d:response>
    <d:response>
        <d:href>/calendars/johndoe/home/fancy-caldav-client-1234253678.ics</d:href>
        <d:propstat>
            <d:prop>
                <d:getetag>"5-12"</d:getetag>
            </d:prop>
            <d:status>HTTP/1.1 200 OK</d:status>
        </d:propstat>
    </d:response>
</d:multistatus>

Judging from this last request, 3 things have changed:

  • The etag for the task has changed, so the contents must be different
  • There's a new url, some other client must have added an object
  • One object is missing, something must have deleted it.

So based on those 3 items we know that we need to delete an object from our local list, and fetch the contents for the new item, and the updated one.

To fetch the data for these, you can simply issue GET requests:

GET /calendars/johndoe/home/132456762153245.ics HTTP/1.1

But, because in a worst-case scenario this could result in a LOT of GET requests we can do a 'multiget'.

REPORT /calendars/johndoe/home/ HTTP/1.1
Depth: 1
Prefer: return-minimal
Content-Type: application/xml; charset=utf-8

<c:calendar-multiget xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
    <d:prop>
        <d:getetag />
        <c:calendar-data />
    </d:prop>
    <d:href>/calendars/johndoe/home/132456762153245.ics</d:href>
    <d:href>/calendars/johndoe/home/fancy-caldav-client-1234253678.ics</d:href>
</c:calendar-multiget>

This request will simply return a multi-status again with the calendar-data and etag.

A small note about application design

If you read this far and understood what's been said, you may have realized that it's a bit cumbersome to have a separate step for the initial sync, and subsequent updates.

It would totally be possible to skip the 'initial sync', and just use calendar-query and calendar-multiget REPORTS for the initial sync as well.

Updating a calendar object

Updating a calendar object is rather simple:

PUT /calendars/johndoe/home/132456762153245.ics HTTP/1.1
Content-Type: text/calendar; charset=utf-8
If-Match: "2134-314"

BEGIN:VCALENDAR
....
END:VCALENDAR

A response to this will be something like this:

HTTP/1.1 204 No Content
ETag: "2134-315"

The update gave us back the new ETag. SabreDAV gives this ETag on updates back most of the time, but not always.

There are cases where the caldav server must modify the iCalendar object right after storage. In those cases an ETag will not be returned, and you should issue a GET request immediately to get the correct object.

A few notes:

  • You must not change the UID of the original object
  • Every object should hold only 1 event or task.
  • You cannot change an VEVENT into a VTODO.

Creating a calendar object

Creating a calendar object is almost identical. You have to choose the new url you have to choose the url for the new object.

PUT /calendars/johndoe/home/somerandomstring.ics HTTP/1.1
Content-Type: text/calendar; charset=utf-8

BEGIN:VCALENDAR
....
END:VCALENDAR

A response to this will be something like this:

HTTP/1.1 201 Created
ETag: "21345-324"

Similar to updating, an ETag is often returned, but there are cases where this is not true.

Deleting a calendar object

Deleting is simple enough:

DELETE /calendars/johndoe/home/132456762153245.ics HTTP/1.1
If-Match: "2134-314"

Discovery

Ideally you will want to make sure that all the calendars in an account are automatically discovered. The best user interface would be to just have to ask for three items:

  • Username
  • Password
  • Server

And the server should be as short as possible. This is possible with most servers.

If, for example a user specified 'dav.example.org' for the server, the first thing you should do is attempt to send a PROPFIND request to https://dav.example.org/. Note that you SHOULD try the https url before the http url.

This PROPFIND request looks as follows:

PROPFIND / HTTP/1.1
Depth: 0
Prefer: return-minimal
Content-Type: application/xml; charset=utf-8

<d:propfind xmlns:d="DAV:">
  <d:prop>
     <d:current-user-principal />
  </d:prop>
</d:propfind>

This will return a response such as the following:

HTTP/1.1 207 Multi-status
Content-Type: application/xml; charset=utf-8

<d:multistatus xmlns:d="DAV:" xmlns:cs="http://calendarserver.org/ns/">
    <d:response>
        <d:href>/</d:href>
        <d:propstat>
            <d:prop>
                <d:current-user-principal>
                    <d:href>/principals/users/johndoe/</d:href>
                </d:current-user-principal>
            </d:prop>
            <d:status>HTTP/1.1 200 OK</d:status>
        </d:propstat>
    </d:response>
</d:multistatus>

A 'principal' is a user. The url that's being returned, is a url that refers to the current user. On this url you can request additional information about the user.

What we need from this url, is their 'calendar home'. The calendar home is a collection that contains all of the users' calendars.

To request that, issue the following request:

PROPFIND /principals/users/johndoe/ HTTP/1.1
Depth: 0
Prefer: return-minimal
Content-Type: application/xml; charset=utf-8

<d:propfind xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
  <d:prop>
     <d:calendar-home-set />
  </d:prop>
</d:propfind>

This will return a response such as the following:

HTTP/1.1 207 Multi-status
Content-Type: application/xml; charset=utf-8

<d:multistatus xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
    <d:response>
        <d:href>/</d:href>
        <d:propstat>
            <d:prop>
                <c:calendar-home-set>
                    <d:href>/calendars/johndoe/</d:href>
                </c:calendar-home-set>
            </d:prop>
            <d:status>HTTP/1.1 200 OK</d:status>
        </d:propstat>
    </d:response>
</d:multistatus>

Lastly, to list all the calendars for the user, issue a PROPFIND request with Depth: 1.

PROPFIND /calendars/johndoe/ HTTP/1.1
Depth: 1
Prefer: return-minimal
Content-Type: application/xml; charset=utf-8

<d:propfind xmlns:d="DAV:" xmlns:cs="http://calendarserver.org/ns/" xmlns:c="urn:ietf:params:xml:ns:caldav">
  <d:prop>
     <d:resourcetype />
     <d:displayname />
     <cs:getctag />
     <c:supported-calendar-component-set />
  </d:prop>
</d:propfind>

In that last request, we asked for 4 properties.

The resourcetype tells us what type of object we're getting back. You must read out the resourcetype and ensure that it contains at least a calendar element in the CalDAV namespace. Other items may be returned, including non- calendar, which your application should ignore.

The displayname is a human-readable string for the calendarname, the ctag was already covered in an earlier chapter.

Lastly, supported-calendar-component-set. This gives us a list of components that the claendar accepts. This could be just VTODO, VEVENT, VJOURNAL or a combination of these three.

If you are just creating a todo-list application, this means you should only list the calendars that support the VTODO component.

HTTP/1.1 207 Multi-status
Content-Type: application/xml; charset=utf-8

<d:multistatus xmlns:d="DAV:" xmlns:cs="http://calendarserver.org/ns/" xmlns:c="urn:ietf:params:xml:ns:caldav">
    <d:response>
        <d:href>/calendars/johndoe/</d:href>
        <d:propstat>
            <d:prop>
                <d:resourcetype>
                    <d:collection/>
                </d:resourcetype>
            </d:prop>
            <d:status>HTTP/1.1 200 OK</d:status>
        </d:propstat>
    </d:response>
    <d:response>
        <d:href>/calendars/johndoe/home/</d:href>
        <d:propstat>
            <d:prop>
                <d:resourcetype>
                    <d:collection/>
                    <c:calendar/>
                </d:resourcetype>
                <d:displayname>Home calendar</d:displayname>
                <cs:getctag>3145</cs:getctag>
                <c:supported-calendar-component-set>
                    <c:comp name="VTODO" />
                </c:supported-component-set>
            </d:prop>
            <d:status>HTTP/1.1 200 OK</d:status>
        </d:propstat>
    </d:response>
    <d:response>
        <d:href>/calendars/johndoe/tasks/</d:href>
        <d:propstat>
            <d:prop>
                <d:resourcetype>
                    <d:collection/>
                    <c:calendar/>
                </d:resourcetype>
                <d:displayname>My TODO list</d:displayname>
                <cs:getctag>3345</cs:getctag>
                <c:supported-calendar-component-set>
                    <c:comp name="VTODO" />
                </c:supported-component-set>
            </d:prop>
            <d:status>HTTP/1.1 200 OK</d:status>
        </d:propstat>
    </d:response>
</d:multistatus>

Advanced discovery topics

See ServiceDiscovery.


内容概要:本文详细介绍了一种基于节点不连续伽辽金方法(Discontinuous Galerkin Method)在求解线性和非线性平流方程中的一维数值实现方案,并提供了完整的MATLAB代码实现。该方法在处理偏微分方程特别是具有间断解或高梯度特征的问题时展现出优异的稳定性和精度。文中系统阐述了算法的核心原理、空间离散化策略、时间推进机制以及边界条件的处理方式,通过具体编程实例展示如何在MATLAB环境中实现该数值方法,并辅以典型算例验证其有效性和可靠性。此外,文章还强调科研工作中“借力”与创新思维的重要性,鼓励研究者在夯实理论基础的同时勇于探索新思路。; 适合人群:具备偏微分方程数值解法基础知识、熟悉MATLAB编程,从事计算数学、流体力学、物理建模及相关领域的研究生、科研人员及工程技术开发者。; 使用场景及目标:① 学习并掌握节点不连续伽辽金方法的基本理论与实现流程;② 利用所提供的MATLAB代码开展线性和非线性平流方程的数值模拟实验;③ 将该方法作为基础算法应用于高分辨率数值模拟、守恒律方程求解等科研项目中的扩展与改进; 阅读建议:建议读者结合经典数值分析教材深入理解DG方法的数学背景,逐段调试并运行所附MATLAB代码,通过调整初始条件、网格划分和时间步长等方式观察算法表现,从而深化对数值稳定性与计算精度之间平衡关系的理解。
内容概要:本文档聚焦于【博士论文复现】光伏并网逆变器序阻抗建模、扫频辨识与弱电网交互稳定性分析,提供了完整的Matlab代码与Simulink仿真实现方案。内容涵盖基于谐波线性化的并网VSG逆变器正负序阻抗建模、锁相环与电流环的小信号建模、扫频法辨识系统阻抗、奈奎斯特稳定性判据的应用,以及在弱电网条件下逆变器与电网交互稳定性的仿真验证全过程。通过理论推导与仿真实践相结合,帮助读者掌握新能源并网系统稳定性分析的核心技术与工程实现方法。; 适合人群:具备电力电子、自动控制理论基础,熟悉Matlab/Simulink环境,从事新能源发电、并网控制或电力系统稳定性研究的研究生、科研人员及工程师。; 使用场景及目标:① 复现博士论文中关于光伏并网逆变器阻抗建模与稳定性分析的关键实验;② 学习并掌握扫频法(Frequency Scan)在实际系统中的应用技巧;③ 利用提供的模型进行弱电网下并网系统稳定性的仿真研究与故障机理分析;④ 作为相关课题研究或毕业设计的技术参考与代码基础。; 阅读建议:此资源以博士论文级别的科研内容为核心,不仅提供可运行的代码,更强调理论与实践的紧密结合。建议使用者首先梳理文档中的理论框架,再逐步运行和调试仿真模型,重点关注扫频激励信号的设计、阻抗数据的提取与稳定性判据的判断逻辑,从而深刻理解并网逆变器在复杂电网环境下的动态交互行为。
(一)前端报名页面功能 自定义表单生成 后台拖拽式配置报名表单,按需增删字段,设置字段必填 / 选填、字段提示文字、输入长度限制;支持多表单模板,可搭建招生报名、活动签到、求职投递、团购预约多套独立报名页。 数据格式强校验 原生 PHP 校验手机号、身份证、邮箱、数字格式,非法输入实时拦截,避免脏数据入库;搭配图形验证码,防止机器批量刷报名。 文件上传附件 支持图片、PDF、Word 等材料上传(简历、证件、报名表),后台统一管理附件,限制上传大小与文件类型。 自动回执提示 用户提交成功后展示自定义成功文案,支持弹窗 / 跳转页面两种提示;可设置自动展示报名编号(内置唯一 ID 生成脚本)。 兼容拦截工具 集成广告拦截检测脚本,访客开启广告拦截时友好提示关闭,保证表单正常提交;兼容代理访问用户,后台可查看访客代理 IP 记录。 (二)后台管理核心功能 账号密码安全防护 后台登录页采用 PHP 简易密码保护脚本,独立管理密码,支持多管理员账号、分级权限(普通查看 / 完整编辑 / 数据删除)。 实时报名数据管理 列表分页展示所有报名记录,支持按姓名、电话、报名时间、表单字段模糊搜索; 支持单条编辑、删除、标记已联系 / 已核销 / 无效报名; 批量操作:批量导出 Excel、批量删除、批量修改状态。 在线实时统计 内置在线访客统计脚本,后台首页实时查看当前访问报名页在线人数;自动生成报名数据统计图(每日报名量、字段占比)。 数据导出与打印 一键导出全部 / 筛选后的报名数据为 Excel 文件,包含所有填写字段、上传附件链接、提交时间、访客 IP;支持打印纸质签到表。 消息自动通知 管理员通知:用户提交报名后,自动发送短信 / 邮件提醒管理员有新报名; 用户回执:提交成功自动给用户手机 / 邮箱发送报名凭证、报名编号。 防刷限流配置 后台可自定义限制:同一 IP X 分钟内仅
标题基于SpringBoot的智能家居控制系统设计与实现AI更换标题第1章引言介绍智能家居控制系统的研究背景、意义、现状以及论文的方法和创新点。1.1研究背景与意义阐述智能家居控制系统的发展背景及研究意义。1.2国内外研究现状分析国内外智能家居控制系统的发展现状与趋势。1.3研究方法以及创新点概述本文采用的研究方法及主要创新点。第2章相关理论介绍智能家居控制系统设计与实现的相关理论。2.1SpringBoot框架概述介绍SpringBoot框架的特点、优势及其在智能家居控制系统中的应用。2.2智能家居技术基础阐述智能家居系统的基本组成、通信协议及关键技术。2.3数据库技术介绍数据库设计原则及在智能家居控制系统中的应用。第3章系统需求分析与设计详细分析智能家居控制系统的需求,并给出系统设计方案。3.1功能需求分析分析智能家居控制系统应具备的功能,如远程控制、设备管理等。3.2性能需求分析阐述系统对性能的要求,如响应时间、并发处理能力等。3.3系统架构设计给出系统的整体架构,包括前端、后端及数据库设计。第4章系统实现与测试详细介绍智能家居控制系统的实现过程及测试方法。4.1系统开发环境介绍系统开发所需的硬件、软件环境及开发工具。4.2系统实现过程详细阐述系统各模块的实现过程,包括代码实现、接口设计等。4.3系统测试方法介绍系统测试的方法、步骤及测试用例设计。第5章系统优化与改进针对系统测试中发现的问题,提出优化与改进方案。5.1性能优化对系统性能进行优化,提高系统响应速度和并发处理能力。5.2功能增强根据用户需求,增加新的功能模块或改进现有功能。5.3安全性提升加强系统安全性,防止数据泄露和非法访问。第6章结论与展望总结本文的研究成果,并展望未来的研究方向。6.1研究结论概括本文的主要研究成果,包括系统实现的功能、性能优化效果等。6.2展望指出本文研究的不足之处,提出未来研究的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值