use http::{StatusCode, Uri}; use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder}; use hyper_util::client::legacy::Client; use hyper_util::client::legacy::connect::HttpConnector; use hyper_util::rt::TokioExecutor; use icalendar::{Calendar, CalendarComponent}; use libdav::caldav::{FindCalendarHomeSet, FindCalendars, GetCalendarResources}; use libdav::dav::{FoundCollection, PutResource, PutResourceResponse, WebDavClient, WebDavError}; use libdav::{CalDavClient, FetchedResource}; use rmcp::ErrorData; use rmcp::model::ErrorCode; use thiserror::Error; use tower_http::auth::AddAuthorization; use tracing::event; use tracing::Level; use uuid::Uuid; pub(crate) type AuthorizedCaldavClient = CalDavClient, String>>>; pub(crate) fn get_caldav_client( webdav_uri: Uri, username: &str, password: &str, ) -> AuthorizedCaldavClient { let https_connector: HttpsConnector = HttpsConnectorBuilder::new() .with_native_roots() .unwrap() .https_only() .enable_http1() .build(); let https_client: Client, _> = Client::builder(TokioExecutor::new()).build(https_connector); let https_client_with_auth = AddAuthorization::basic(https_client, username, password); CalDavClient::new(WebDavClient::new(webdav_uri, https_client_with_auth)) } #[derive(Error, Debug)] pub(crate) enum CalendarError { #[error("failed to get user principal")] UserPrincipalError( #[from] libdav::dav::FindCurrentUserPrincipalError, ), #[error("User Principal does not exist")] NoUserPrincipal, #[error("webdav error")] WebDavError(#[from] WebDavError), #[error("unavailable resource")] UnavailableResourceError(StatusCode), #[error("failed Parsing a caldav resource")] ParsingError(String), } impl From for ErrorData { fn from(value: CalendarError) -> Self { use crate::caldav::client::CalendarError::*; match value { UserPrincipalError(e) => ErrorData::new(ErrorCode::INTERNAL_ERROR, e.to_string(), None), NoUserPrincipal => ErrorData::new( ErrorCode::RESOURCE_NOT_FOUND, "no user principal".to_string(), None, ), WebDavError(e) => ErrorData::new(ErrorCode::INTERNAL_ERROR, e.to_string(), None), UnavailableResourceError(e) => { ErrorData::new(ErrorCode::RESOURCE_NOT_FOUND, e.to_string(), None) } ParsingError(e) => ErrorData::new(ErrorCode::INTERNAL_ERROR, e.to_string(), None), } } } pub(crate) async fn find_calendars( caldav_client: &AuthorizedCaldavClient, ) -> Result, CalendarError> { use CalendarError::*; let user_principal = caldav_client .find_current_user_principal() .await? .ok_or(NoUserPrincipal)?; let calendar_home_sets = caldav_client .request(FindCalendarHomeSet::new(&user_principal)) .await? .home_sets; Ok(caldav_client .request(FindCalendars::new(&calendar_home_sets[0])) .await? .calendars) } pub(crate) async fn get_calendar_resources( caldav_client: &AuthorizedCaldavClient, calendar: &FoundCollection, ) -> Result, CalendarError> { let calendar_resources = caldav_client .request(GetCalendarResources::new(calendar.href.as_str())) .await .map_err(CalendarError::WebDavError)? .resources; Ok(calendar_resources) } pub(crate) async fn upload_components( authorized_client: &AuthorizedCaldavClient, calendar: &FoundCollection, components: Vec>, ) -> Result { event!(Level::INFO, "uploading components to {}", calendar.href); let mut upload_calendar = Calendar::new(); for todo in components { upload_calendar.push(todo); } Ok(authorized_client .request( PutResource::new(format!("{}{}.ics", calendar.href, Uuid::new_v4()).as_str()) .create(upload_calendar.to_string().leak(), "text/caldav"), ) .await?) } pub(crate) async fn get_components( authorized_client: &AuthorizedCaldavClient, calendar: &FoundCollection, ) -> Result, CalendarError> { use CalendarError::*; let mut components: Vec = Vec::new(); let resources = get_calendar_resources(authorized_client, calendar).await?; for i in resources { let calendar: Calendar = i .content .map_err(UnavailableResourceError)? .data .parse() .map_err(ParsingError)?; components.extend(calendar.components); } Ok(components) }