152 lines
4.8 KiB
Rust
152 lines
4.8 KiB
Rust
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<AddAuthorization<Client<HttpsConnector<HttpConnector>, String>>>;
|
|
|
|
pub(crate) fn get_caldav_client(
|
|
webdav_uri: Uri,
|
|
username: &str,
|
|
password: &str,
|
|
) -> AuthorizedCaldavClient {
|
|
let https_connector: HttpsConnector<HttpConnector> = HttpsConnectorBuilder::new()
|
|
.with_native_roots()
|
|
.unwrap()
|
|
.https_only()
|
|
.enable_http1()
|
|
.build();
|
|
|
|
let https_client: Client<HttpsConnector<HttpConnector>, _> =
|
|
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<hyper_util::client::legacy::Error>,
|
|
),
|
|
#[error("User Principal does not exist")]
|
|
NoUserPrincipal,
|
|
#[error("webdav error")]
|
|
WebDavError(#[from] WebDavError<hyper_util::client::legacy::Error>),
|
|
#[error("unavailable resource")]
|
|
UnavailableResourceError(StatusCode),
|
|
#[error("failed Parsing a caldav resource")]
|
|
ParsingError(String),
|
|
}
|
|
|
|
impl From<CalendarError> 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<Vec<FoundCollection>, 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<Vec<FetchedResource>, 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<impl Into<CalendarComponent>>,
|
|
) -> Result<PutResourceResponse, CalendarError> {
|
|
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<Vec<CalendarComponent>, CalendarError> {
|
|
use CalendarError::*;
|
|
|
|
let mut components: Vec<CalendarComponent> = 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)
|
|
}
|