libdav 0.10.3

CalDAV and CardDAV client implementations.
Documentation
// Copyright 2023-2024 Hugo Osvaldo Barrera
//
// SPDX-License-Identifier: ISC

//! An example of some basic usage of the `CalDavClient` type.
//!
//! Usage:
//!
//!     cargo run --example=find_calendars https://example.com user@example.com MYPASSWORD
//!     cargo run --example=find_calendars $SERVER_URL         $USERNAME        $PASSWORD
//!
//! Example output (with `$1 = "https://fastmail.com"`):
//!
//! ```
//! Resolved server URL to: https://d277161.caldav.fastmail.com/dav/calendars
//! found 1 calendars...
//! 📅 name: Some("Calendar"), colour: Some("#3a429c"), path: "/dav/calendars/user/vdirsyncer@fastmail.com/cc396171-0227-4e1c-b5ee-d42b5e17d533/"
//! Href and Etag for components in calendar:
//! - /dav/calendars/user/vdirsyncer@fastmail.com/cc396171-0227-4e1c-b5ee-d42b5e17d533/395b00a0-eebc-40fd-a98e-176a06367c82.ics, "e7577ff2b0924fe8e9a91d3fb2eb9072598bf9fb"
//! ```
use http::Uri;
use hyper_rustls::HttpsConnectorBuilder;
use hyper_util::client::legacy::Client;
use hyper_util::rt::TokioExecutor;
use libdav::caldav::{FindCalendarHomeSet, FindCalendars, GetSupportedComponents};
use libdav::dav::{GetProperty, ListResources, WebDavClient};
use libdav::{CalDavClient, names};
use tower_http::auth::AddAuthorization;

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let mut arguments = std::env::args();
    arguments
        .next()
        .expect("binary has been called with a name");
    let base_url: Uri = arguments
        .next()
        .expect("$1 is defined")
        .parse()
        .expect("$1 is a valid URL");
    let username = arguments.next().expect("$2 is a valid username");
    let password = arguments.next().expect("$3 is a valid password");

    let https_connector = HttpsConnectorBuilder::new()
        .with_native_roots()
        .expect("native TLS roots should be available")
        .https_or_http()
        .enable_http1()
        .build();
    let https_client = Client::builder(TokioExecutor::new()).build(https_connector);
    let https_client = AddAuthorization::basic(https_client, &username, &password);
    let webdav = WebDavClient::new(base_url, https_client);
    let caldav_client = CalDavClient::bootstrap_via_service_discovery(webdav)
        .await
        .unwrap();

    let urls = match caldav_client.find_current_user_principal().await.unwrap() {
        Some(principal) => {
            let home_set = caldav_client
                .request(FindCalendarHomeSet::new(&principal))
                .await
                .unwrap()
                .home_sets;
            if home_set.is_empty() {
                vec![caldav_client.base_url().clone()]
            } else {
                home_set
            }
        }
        None => vec![caldav_client.base_url().clone()],
    };

    for url in urls {
        let calendars = caldav_client
            .request(FindCalendars::new(&url))
            .await
            .unwrap()
            .calendars;

        println!("found {} calendars...", calendars.len());

        for calendar in calendars {
            let name = caldav_client
                .request(GetProperty::new(&calendar.href, &names::DISPLAY_NAME))
                .await
                .unwrap()
                .value;
            let color = caldav_client
                .request(GetProperty::new(&calendar.href, &names::CALENDAR_COLOUR))
                .await
                .unwrap()
                .value;
            let comps = caldav_client
                .request(GetSupportedComponents::new(&calendar.href))
                .await
                .unwrap()
                .components;
            let capabilities = if comps.is_empty() {
                "(none advertised)".to_string()
            } else {
                comps
                    .iter()
                    .map(|c| c.as_str().to_string())
                    .collect::<Vec<_>>()
                    .join(", ")
            };
            println!(
                "📅 name: {name:?}, colour: {color:?}, path: {:?}, etag: {:?}, capabilities: {}",
                &calendar.href, &calendar.etag, capabilities
            );

            let items = caldav_client
                .request(ListResources::new(&calendar.href))
                .await
                .unwrap()
                .resources
                .into_iter()
                .filter(|i| !i.resource_type.is_collection);
            for item in items {
                println!("   {}, {}", item.href, item.etag.unwrap());
            }
        }
    }
}