Re-implement calendar export

This commit is contained in:
Lennart K
2026-01-08 15:36:02 +01:00
parent 5639127782
commit 977fd75500
7 changed files with 82 additions and 86 deletions

2
Cargo.lock generated
View File

@@ -1771,7 +1771,7 @@ dependencies = [
[[package]] [[package]]
name = "ical" name = "ical"
version = "0.11.0" version = "0.11.0"
source = "git+https://github.com/lennart-k/ical-rs?branch=dev#0a50f3998b8ae104642cceb9d974e99b78838b14" source = "git+https://github.com/lennart-k/ical-rs?branch=dev#8732224dd1514f6dcbccdcf63268a3f223d360e9"
dependencies = [ dependencies = [
"chrono", "chrono",
"chrono-tz", "chrono-tz",

View File

@@ -5,11 +5,11 @@ use axum::extract::State;
use axum::{extract::Path, response::Response}; use axum::{extract::Path, response::Response};
use headers::{ContentType, HeaderMapExt}; use headers::{ContentType, HeaderMapExt};
use http::{HeaderValue, Method, StatusCode, header}; use http::{HeaderValue, Method, StatusCode, header};
use ical::component::IcalCalendar;
use ical::generator::Emitter; use ical::generator::Emitter;
use ical::property::ContentLine; use ical::property::ContentLine;
use percent_encoding::{CONTROLS, utf8_percent_encode}; use percent_encoding::{CONTROLS, utf8_percent_encode};
use rustical_store::{CalendarStore, SubscriptionStore, auth::Principal}; use rustical_store::{CalendarStore, SubscriptionStore, auth::Principal};
use std::collections::HashMap;
use std::str::FromStr; use std::str::FromStr;
use tracing::instrument; use tracing::instrument;
@@ -31,79 +31,62 @@ pub async fn route_get<C: CalendarStore, S: SubscriptionStore>(
return Err(crate::Error::Unauthorized); return Err(crate::Error::Unauthorized);
} }
// let mut vtimezones = HashMap::new(); let objects = cal_store
// let objects = cal_store.get_objects(&principal, &calendar_id).await?; .get_objects(&principal, &calendar_id)
.await?
.into_iter()
.map(|(_, object)| object.into())
.collect();
todo!() let mut props = vec![];
// let mut ical_calendar_builder = IcalCalendarBuilder::version("2.0") if let Some(displayname) = calendar.meta.displayname {
// .gregorian() props.push(ContentLine {
// .prodid("RustiCal"); name: "X-WR-CALNAME".to_owned(),
// if let Some(displayname) = calendar.meta.displayname { value: Some(displayname),
// ical_calendar_builder = ical_calendar_builder.set(ContentLine { params: vec![].into(),
// name: "X-WR-CALNAME".to_owned(), });
// value: Some(displayname), }
// params: vec![].into(), if let Some(description) = calendar.meta.description {
// }); props.push(ContentLine {
// } name: "X-WR-CALDESC".to_owned(),
// if let Some(description) = calendar.meta.description { value: Some(description),
// ical_calendar_builder = ical_calendar_builder.set(ContentLine { params: vec![].into(),
// name: "X-WR-CALDESC".to_owned(), });
// value: Some(description), }
// params: vec![].into(), if let Some(color) = calendar.meta.color {
// }); props.push(ContentLine {
// } name: "X-WR-CALCOLOR".to_owned(),
// if let Some(timezone_id) = calendar.timezone_id { value: Some(color),
// ical_calendar_builder = ical_calendar_builder.set(ContentLine { params: vec![].into(),
// name: "X-WR-TIMEZONE".to_owned(), });
// value: Some(timezone_id), }
// params: vec![].into(), if let Some(timezone_id) = calendar.timezone_id {
// }); props.push(ContentLine {
// } name: "X-WR-TIMEZONE".to_owned(),
// value: Some(timezone_id),
// for object in &objects { params: vec![].into(),
// vtimezones.extend(object.get_vtimezones()); });
// match object.get_data() { }
// CalendarObjectComponent::Event(EventObject { event, .. }, overrides) => {
// ical_calendar_builder = ical_calendar_builder let export_calendar = IcalCalendar::from_objects(objects, props);
// .add_event(event.clone())
// .add_events(overrides.iter().map(|ev| ev.event.clone())); let mut resp = Response::builder().status(StatusCode::OK);
// } let hdrs = resp.headers_mut().unwrap();
// CalendarObjectComponent::Todo(todo, overrides) => { hdrs.typed_insert(ContentType::from_str("text/calendar; charset=utf-8").unwrap());
// ical_calendar_builder = ical_calendar_builder
// .add_todo(todo.clone()) let filename = format!("{}_{}.ics", calendar.principal, calendar.id);
// .add_todos(overrides.iter().cloned()); let filename = utf8_percent_encode(&filename, CONTROLS);
// } hdrs.insert(
// CalendarObjectComponent::Journal(journal, overrides) => { header::CONTENT_DISPOSITION,
// ical_calendar_builder = ical_calendar_builder HeaderValue::from_str(&format!(
// .add_journal(journal.clone()) "attachement; filename*=UTF-8''{filename}; filename={filename}",
// .add_journals(overrides.iter().cloned()); ))
// } .unwrap(),
// } );
// } if matches!(method, Method::HEAD) {
// Ok(resp.body(Body::empty()).unwrap())
// ical_calendar_builder = ical_calendar_builder.add_timezones(vtimezones.into_values().cloned()); } else {
// Ok(resp.body(Body::new(export_calendar.generate())).unwrap())
// let ical_calendar = ical_calendar_builder }
// .build()
// .map_err(|parser_error| Error::IcalError(parser_error.into()))?;
//
// let mut resp = Response::builder().status(StatusCode::OK);
// let hdrs = resp.headers_mut().unwrap();
// hdrs.typed_insert(ContentType::from_str("text/calendar; charset=utf-8").unwrap());
//
// let filename = format!("{}_{}.ics", calendar.principal, calendar.id);
// let filename = utf8_percent_encode(&filename, CONTROLS);
// hdrs.insert(
// header::CONTENT_DISPOSITION,
// HeaderValue::from_str(&format!(
// "attachement; filename*=UTF-8''{filename}; filename={filename}",
// ))
// .unwrap(),
// );
// if matches!(method, Method::HEAD) {
// Ok(resp.body(Body::empty()).unwrap())
// } else {
// Ok(resp.body(Body::new(ical_calendar.generate())).unwrap())
// }
} }

View File

@@ -46,6 +46,9 @@ pub async fn route_import<C: CalendarStore, S: SubscriptionStore>(
let description = cal let description = cal
.get_property("X-WR-CALDESC") .get_property("X-WR-CALDESC")
.and_then(|prop| prop.value.clone()); .and_then(|prop| prop.value.clone());
let color = cal
.get_property("X-WR-CALCOLOR")
.and_then(|prop| prop.value.clone());
let timezone_id = cal let timezone_id = cal
.get_property("X-WR-TIMEZONE") .get_property("X-WR-TIMEZONE")
.and_then(|prop| prop.value.clone()); .and_then(|prop| prop.value.clone());

View File

@@ -92,7 +92,7 @@ pub async fn route_mkcalendar<C: CalendarStore, S: SubscriptionStore>(
.ok_or_else(|| rustical_dav::Error::BadRequest("No timezone data provided".to_owned()))? .ok_or_else(|| rustical_dav::Error::BadRequest("No timezone data provided".to_owned()))?
.map_err(|_| rustical_dav::Error::BadRequest("Error parsing timezone".to_owned()))?; .map_err(|_| rustical_dav::Error::BadRequest("Error parsing timezone".to_owned()))?;
let timezone = calendar.vtimezones.first().ok_or_else(|| { let timezone = calendar.vtimezones.values().next().ok_or_else(|| {
rustical_dav::Error::BadRequest("No timezone data provided".to_owned()) rustical_dav::Error::BadRequest("No timezone data provided".to_owned())
})?; })?;
let timezone: Option<chrono_tz::Tz> = timezone.into(); let timezone: Option<chrono_tz::Tz> = timezone.into();

View File

@@ -215,7 +215,7 @@ impl Resource for CalendarResource {
) )
})?; })?;
let timezone = calendar.vtimezones.first().ok_or_else(|| { let timezone = calendar.vtimezones.values().next().ok_or_else(|| {
rustical_dav::Error::BadRequest("No timezone data provided".to_owned()) rustical_dav::Error::BadRequest("No timezone data provided".to_owned())
})?; })?;
let timezone: Option<chrono_tz::Tz> = timezone.into(); let timezone: Option<chrono_tz::Tz> = timezone.into();

View File

@@ -4,6 +4,7 @@ use super::prop::{
}; };
use crate::Error; use crate::Error;
use derive_more::derive::{From, Into}; use derive_more::derive::{From, Into};
use ical::generator::Emitter;
use rustical_dav::{ use rustical_dav::{
extensions::CommonPropertiesExtension, extensions::CommonPropertiesExtension,
privileges::UserPrivilegeSet, privileges::UserPrivilegeSet,
@@ -53,15 +54,18 @@ impl Resource for CalendarObjectResource {
CalendarObjectProp::Getetag(self.object.get_etag()) CalendarObjectProp::Getetag(self.object.get_etag())
} }
CalendarObjectPropName::CalendarData(CalendarData { expand, .. }) => { CalendarObjectPropName::CalendarData(CalendarData { expand, .. }) => {
CalendarObjectProp::CalendarData(if let Some(expand) = expand.as_ref() { CalendarObjectProp::CalendarData(expand.as_ref().map_or_else(
todo!() || self.object.get_ics().to_owned(),
// self.object.get_inner().expand_recurrence( |expand| {
// Some(expand.start.to_utc()), self.object
// Some(expand.end.to_utc()), .get_inner()
// ) .expand_recurrence(
} else { Some(expand.start.to_utc()),
self.object.get_ics().to_owned() Some(expand.end.to_utc()),
}) )
.generate()
},
))
} }
CalendarObjectPropName::Getcontenttype => { CalendarObjectPropName::Getcontenttype => {
CalendarObjectProp::Getcontenttype("text/calendar;charset=utf-8") CalendarObjectProp::Getcontenttype("text/calendar;charset=utf-8")

View File

@@ -102,3 +102,9 @@ impl CalendarObject {
(&self.inner).into() (&self.inner).into()
} }
} }
impl From<CalendarObject> for IcalCalendarObject {
fn from(value: CalendarObject) -> Self {
value.inner
}
}