add todo now adds alarms to start and end of todo

more sensible name for submodule `caldav` of `caldav`
This commit is contained in:
2026-05-03 20:28:31 +02:00
parent 3dd897c422
commit 3d8975cfc1
4 changed files with 59 additions and 30 deletions
+149
View File
@@ -0,0 +1,149 @@
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 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> {
println!("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)
}