Существует множество приложений календаря для синхронизации данных календаря. Стоит изучить, как использовать Rust для получения данных CalDav.

Задний план

В нашей повседневной работе мы можем использовать несколько календарей одновременно, один для групповой работы, один для самостоятельной работы, один для семейного TODO и т. д. Иногда трудно следовать расписанию. Итак, в моей компании мы создали небольшой скрипт на Python, чтобы получать все события и TODO, которые произойдут сегодня, и отправлять электронное письмо всем утром. Это действительно полезно, так как это напомнит нам о расписании, особенно нас заметят заранее, а затем организуют их более гибко.

Мы могли бы сделать это, потому что легко создать клиент CalDav для получения данных с сервера CalDav. Это полностью то же самое, что Thunderbird Lightning или другие приложения календаря, они просто подписываются на целевой календарь через URL-адрес календаря. Затем приложения будут автоматически получать новые данные или отправлять данные на сервер. Но в нашем случае нам нужно только получить данные с сервера, отфильтровать события, а затем отправить электронное письмо.

В Python модуль caldav прост в использовании и получении событий из календаря. Всего несколько строк, мы могли бы получить сегодняшние события. Я не буду публиковать туториал для модуля caldav. Пожалуйста, проверьте, если вы заинтересованы.

Но после изучения исходного кода модуля Python caldav он использует модуль requests для получения данных с сервера, который используется для получения/отправки данных с/на URL-адрес HTTP. Тогда почему бы нам не попробовать создать клиент CalDav через Rust? requests в Python работает, тогда reqwest в Rust будет работать.

Подготовка

Есть три вещи, которые необходимо подготовить перед началом получения данных.

  1. URL-адрес CalDav
  2. Имя пользователя для доступа
  3. Пароль для указанного выше имени пользователя

Запросы

CalDav основан на WebDav, который является расширением HTTP. Таким образом, в CalDav используются не только некоторые методы GET, PUT и DELETE, но также реализованы некоторые другие расширенные методы, такие как PROPFIND, REPORT и т. д. Основные методы, которые мы продемонстрируем здесь, — это расширенные методы.

Ниже приведен пример кода для получения событий через модуль Python caldav, и мы проделаем те же шаги в Rust.

client = caldav.DAVClient(url=url, username=username, password=password)
my_principal = client.principal()
calendars = my_principal.calendars()
for cal in calendars:
    events = cal.events()

Запросить принципала

Нам нужны данные о событиях из нашего URL-адреса, но почему нам нужно здесь. На самом деле URL-адрес CalDav — это просто начальный URL-адрес, который поможет нам получить все URL-адреса календаря. Затем мы будем использовать URL-адреса календаря для получения событий из каждого из них. Причина, по которой у нас разные календари, заключается в том, что они используются совместно с текущей учетной записью. Блок-схема получения данных будет выглядеть следующим образом:

Для получения основного ответа основные вещи, которые мы изменим по сравнению с HTTP-запросом, это:

  • Метод запроса, это будет PROPFIND
  • Тип контента запроса, это будет application/xml
  • Дополнительное значение заголовка запроса Depth со значением 0 (Depth сообщит серверу, насколько глубоко мы хотим рекурсивно получать данные).
  • Тело запроса, это будут данные в формате XML с полем запроса, которое называется тегами.

Ниже приведен демонстрационный код того, как выполнить вышеуказанный запрос. Я добавил комментарии для объяснения.

После запуска вышеуказанной функции сервер отправит ответ XML, который будет выглядеть следующим образом:

<d:multistatus xmlns:d=”DAV:”
  xmlns:s=”http://sabredav.org/ns"
  xmlns:cal=”urn:ietf:params:xml:ns:caldav”
  xmlns:cs=”http://calendarserver.org/ns/"
  xmlns:oc=”http://owncloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/USERNAME/</d:href>
    <d:propstat>
      <d:prop>
        <d:current-user-principal>
          <d:href>  
            /remote.php/dav/principals/users/USERNAME/
          </d:href>
        </d:current-user-principal>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>

Обратите внимание, что используемый URL-адрес и URL-адрес, возвращаемый сервером, могут отличаться от моего примера.

Получив ответ от сервера, нам проще получить главный URL. Я буду использовать крейт minidom, потому что minidom будет анализировать XML в древовидную структуру и будет прост в использовании. Я не буду здесь писать код для парсинга XML. Пожалуйста, не стесняйтесь читать minidom doc или проверить ссылку на мой репозиторий, которая указана в конце статьи.

Запросы calendar-home-set URL и Calendars URLs используют тот же метод, что и principal. Я не буду приводить здесь полный код, а покажу только тело запроса.

// request body to PROPFIND calendar-home-set
static HOMESET_BODY: &str = r#”
 <d:propfind xmlns:d=”DAV:” xmlns:c=”urn:ietf:params:xml:ns:caldav” >
   <d:self/>
   <d:prop>
     <c:calendar-home-set />
   </d:prop>
 </d:propfind>
“#;
// request body to PROPFIND calendars
static CAL_BODY: &str = r#”
 <d:propfind xmlns:d=”DAV:” xmlns:c=”urn:ietf:params:xml:ns:caldav” >
   <d:prop>
     <d:displayname />
     <d:resourcetype />
     <c:supported-calendar-component-set />
   </d:prop>
 </d:propfind>
“#;

В ответе есть список календарей со свойствами запроса после получения данных из запроса календарей. Например, ниже приведена одна запись в моем ответном ответе:

<d:response>
 <d:href>/remote.php/dav/calendars/USERNAME/personal/</d:href>
 <d:propstat>
   <d:prop>
     <d:displayname>Personal</d:displayname>
     <d:resourcetype>
       <d:collection/>
       <cal:calendar/>
     </d:resourcetype>
     <cal:supported-calendar-component-set>
       <cal:comp name=”VEVENT”/>
       <cal:comp name=”VTODO”/>
     </cal:supported-calendar-component-set>
   </d:prop>
   <d:status>HTTP/1.1 200 OK</d:status>
 </d:propstat>
</d:response>

Обратите внимание, что некоторые ответы могут быть пустыми, мы можем просто отфильтровать их при анализе.

Запросить события

Теперь мы нашли URL-адреса календаря в приведенном выше запросе календаря. Мы могли бы использовать его для получения наших событий. Прежде чем начать загрузку, проверьте cal:supported-calendar-component-set. В нем перечислены два типа календаря, которые мы можем получить с сервера: VEVENT и VTODO. Это важно, поскольку говорит нам, какие данные мы можем получить.

Для получения событий с одного URL-адреса календаря у нас будут следующие изменения:

  • Используйте метод REPORT
  • Используйте 1 в качестве значения Depth
  • Добавьте фильтр VEVENT в тело XML (мы будем получать только VEVENT данных)
  • Добавить фильтр временного диапазона в тело XML (цель на определенный временной диапазон)

Ниже приведен демонстрационный код для получения событий из URL-адреса календаря.

После запуска кода вывод, как показано ниже. Все данные события отображаются в теге calendar-data. В нем есть сводка, дата, статус и т. д.

<d:multistatus xmlns:d=”DAV:” xmlns:s=”http://sabredav.org/ns"
  xmlns:cal=”urn:ietf:params:xml:ns:caldav”
  xmlns:cs=”http://calendarserver.org/ns/"
  xmlns:oc=”http://owncloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/USERNAME/personal/ownCloud-ICSIDSTRING.ics</d:href>
    <d:propstat>
      <d:prop>
        <d:getetag>&quot;da86fa060d06bfdedf79ea7b6f050af4&quot;   </d:getetag>
        <cal:calendar-data>
          BEGIN:VCALENDAR
          PRODID:-//ownCloud calendar v1.6.3
          VERSION:2.0
          CALSCALE:GREGORIAN
          BEGIN:VEVENT
          CREATED:20200323T021525Z
          DTSTAMP:20200323T021836Z
          LAST-MODIFIED:20200323T021836Z
          UID:OG2XT87F6F6MWLXYVWD3N
          SUMMARY:test event
          CLASS:CONFIDENTIAL
          STATUS:CONFIRMED
          SEQUENCE:4
          RRULE:FREQ=DAILY
          DTSTART;TZID=Asia/Hong_Kong:20200316T000000
          DTEND;TZID=Asia/Hong_Kong:20200316T010000
          END:VEVENT
          BEGIN:VTIMEZONE
          TZID:Asia/Hong_Kong
          BEGIN:STANDARD
          TZOFFSETFROM:+0800
          TZOFFSETTO:+0800
          TZNAME:HKT
          DTSTART:19700101T000000
          END:STANDARD
          END:VTIMEZONE
          END:VCALENDAR
        </cal:calendar-data>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>

Теперь мы успешно получили события из исходного URL-адреса CalDav. Если мы хотим отправить электронное письмо или SMS себе, мы можем добавить содержимое этого события в электронное письмо или сообщение.

Вывод

После приведенного выше объяснения становится ясно, что клиент CalDav полностью аналогичен внешнему приложению в мире HTTP. Еще 10 лет назад мы использовали формат XML (SOAP) для обмена данными между службами. XML на самом деле является одним из стандартных форматов данных. Итак, отличия заключаются в расширенных методах CalDav. Но способ использования полностью такой же, как и другие методы HTTP, просто убедитесь, что BODY является определенным форматом или тегами в CalDav. Для получения более подробной информации о CalDav, пожалуйста, прочитайте RFC4791 или Создание клиента calDav, в которых описаны подробности о клиенте CalDav.

Вы можете найти мой демонстрационный код CalDav в репозитории marshalshi/caldav-client-rust.