mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 22:52:22 +00:00
ical: Work on calendar object data structure
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1598,7 +1598,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ical"
|
||||
version = "0.11.0"
|
||||
source = "git+https://github.com/lennart-k/ical-rs#a911f2d500f9f422934c5ec6afc94586a8814fb5"
|
||||
source = "git+https://github.com/lennart-k/ical-rs#c5fa2217af23ba27ba80295a2c0eb922f08f6c97"
|
||||
dependencies = [
|
||||
"chrono-tz",
|
||||
"serde",
|
||||
|
||||
@@ -74,10 +74,10 @@ pub async fn route_get<C: CalendarStore, S: SubscriptionStore>(
|
||||
timezones.extend(object_timezones);
|
||||
ical_calendar_builder = ical_calendar_builder.add_event(event.clone());
|
||||
}
|
||||
CalendarObjectComponent::Todo(TodoObject { todo, .. }) => {
|
||||
CalendarObjectComponent::Todo(TodoObject(todo)) => {
|
||||
ical_calendar_builder = ical_calendar_builder.add_todo(todo.clone());
|
||||
}
|
||||
CalendarObjectComponent::Journal(JournalObject { journal, .. }) => {
|
||||
CalendarObjectComponent::Journal(JournalObject(journal)) => {
|
||||
ical_calendar_builder = ical_calendar_builder.add_journal(journal.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,12 +78,13 @@ pub async fn put_event<C: CalendarStore>(
|
||||
true
|
||||
};
|
||||
|
||||
let object = match CalendarObject::from_ics(object_id, body) {
|
||||
let object = match CalendarObject::from_ics(body) {
|
||||
Ok(obj) => obj,
|
||||
Err(_) => {
|
||||
return Err(Error::PreconditionFailed(Precondition::ValidCalendarData));
|
||||
}
|
||||
};
|
||||
assert_eq!(object.get_id(), object_id);
|
||||
cal_store
|
||||
.put_object(principal, calendar_id, object, overwrite)
|
||||
.await?;
|
||||
|
||||
@@ -95,10 +95,8 @@ impl AddressObject {
|
||||
let uid = format!("{}-anniversary", self.get_id());
|
||||
|
||||
let year_suffix = year.map(|year| format!(" ({year})")).unwrap_or_default();
|
||||
Some(CalendarObject::from_ics(
|
||||
uid.clone(),
|
||||
format!(
|
||||
r#"BEGIN:VCALENDAR
|
||||
Some(CalendarObject::from_ics(format!(
|
||||
r#"BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
CALSCALE:GREGORIAN
|
||||
PRODID:-//github.com/lennart-k/rustical birthday calendar//EN
|
||||
@@ -116,8 +114,7 @@ DESCRIPTION:💍 {fullname}{year_suffix}
|
||||
END:VALARM
|
||||
END:VEVENT
|
||||
END:VCALENDAR"#,
|
||||
),
|
||||
)?)
|
||||
))?)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
@@ -139,10 +136,8 @@ END:VCALENDAR"#,
|
||||
let uid = format!("{}-birthday", self.get_id());
|
||||
|
||||
let year_suffix = year.map(|year| format!(" ({year})")).unwrap_or_default();
|
||||
Some(CalendarObject::from_ics(
|
||||
uid.clone(),
|
||||
format!(
|
||||
r#"BEGIN:VCALENDAR
|
||||
Some(CalendarObject::from_ics(format!(
|
||||
r#"BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
CALSCALE:GREGORIAN
|
||||
PRODID:-//github.com/lennart-k/rustical birthday calendar//EN
|
||||
@@ -160,8 +155,7 @@ DESCRIPTION:🎂 {fullname}{year_suffix}
|
||||
END:VALARM
|
||||
END:VEVENT
|
||||
END:VCALENDAR"#,
|
||||
),
|
||||
)?)
|
||||
))?)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
|
||||
@@ -6,11 +6,12 @@ use ical::{generator::IcalEvent, parser::Component, property::Property};
|
||||
use rrule::{RRule, RRuleSet};
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct EventObject {
|
||||
pub event: IcalEvent,
|
||||
// If a timezone is None that means that in the VCALENDAR object there's a timezone defined
|
||||
// with that name but its not from the Olson DB
|
||||
pub timezones: HashMap<String, Option<chrono_tz::Tz>>,
|
||||
pub(crate) ics: String,
|
||||
}
|
||||
|
||||
impl EventObject {
|
||||
@@ -239,11 +240,7 @@ END:VEVENT\r\n",
|
||||
|
||||
#[test]
|
||||
fn test_expand_recurrence() {
|
||||
let event = CalendarObject::from_ics(
|
||||
"318ec6503573d9576818daf93dac07317058d95c".to_string(),
|
||||
ICS.to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
let event = CalendarObject::from_ics(ICS.to_string()).unwrap();
|
||||
let event = event.event().unwrap();
|
||||
|
||||
let events: Vec<String> = event
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use derive_more::From;
|
||||
use ical::parser::ical::component::IcalJournal;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JournalObject {
|
||||
pub journal: IcalJournal,
|
||||
pub(crate) ics: String,
|
||||
}
|
||||
#[derive(Debug, Clone, From)]
|
||||
pub struct JournalObject(pub IcalJournal);
|
||||
|
||||
@@ -5,6 +5,7 @@ use chrono::DateTime;
|
||||
use chrono::Utc;
|
||||
use derive_more::Display;
|
||||
use ical::generator::{Emitter, IcalCalendar};
|
||||
use ical::property::Property;
|
||||
use serde::Serialize;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{collections::HashMap, io::BufReader};
|
||||
@@ -58,15 +59,21 @@ pub enum CalendarObjectComponent {
|
||||
Journal(JournalObject),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CalendarObject {
|
||||
id: String,
|
||||
impl Default for CalendarObjectComponent {
|
||||
fn default() -> Self {
|
||||
Self::Event(EventObject::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CalendarObject<const VERIFIED: bool = true> {
|
||||
data: CalendarObjectComponent,
|
||||
cal: IcalCalendar,
|
||||
properties: Vec<Property>,
|
||||
ics: String,
|
||||
}
|
||||
|
||||
impl CalendarObject {
|
||||
pub fn from_ics(object_id: String, ics: String) -> Result<Self, Error> {
|
||||
pub fn from_ics(ics: String) -> Result<Self, Error> {
|
||||
let mut parser = ical::IcalParser::new(BufReader::new(ics.as_bytes()));
|
||||
let cal = parser.next().ok_or(Error::MissingCalendar)??;
|
||||
if parser.next().is_some() {
|
||||
@@ -94,41 +101,23 @@ impl CalendarObject {
|
||||
.map(|timezone| (timezone.get_tzid().to_owned(), (&timezone).try_into().ok()))
|
||||
.collect();
|
||||
|
||||
if let Some(event) = cal.events.first() {
|
||||
return Ok(CalendarObject {
|
||||
id: object_id,
|
||||
cal: cal.clone(),
|
||||
data: CalendarObjectComponent::Event(EventObject {
|
||||
event: event.clone(),
|
||||
timezones,
|
||||
ics,
|
||||
}),
|
||||
});
|
||||
}
|
||||
if let Some(todo) = cal.todos.first() {
|
||||
return Ok(CalendarObject {
|
||||
id: object_id,
|
||||
cal: cal.clone(),
|
||||
data: CalendarObjectComponent::Todo(TodoObject {
|
||||
todo: todo.clone(),
|
||||
ics,
|
||||
}),
|
||||
});
|
||||
}
|
||||
if let Some(journal) = cal.journals.first() {
|
||||
return Ok(CalendarObject {
|
||||
id: object_id,
|
||||
cal: cal.clone(),
|
||||
data: CalendarObjectComponent::Journal(JournalObject {
|
||||
journal: journal.clone(),
|
||||
ics,
|
||||
}),
|
||||
});
|
||||
}
|
||||
let data = if let Some(event) = cal.events.into_iter().next() {
|
||||
CalendarObjectComponent::Event(EventObject { event, timezones })
|
||||
} else if let Some(todo) = cal.todos.into_iter().next() {
|
||||
CalendarObjectComponent::Todo(todo.into())
|
||||
} else if let Some(journal) = cal.journals.into_iter().next() {
|
||||
CalendarObjectComponent::Journal(journal.into())
|
||||
} else {
|
||||
return Err(Error::InvalidData(
|
||||
"iCalendar component type not supported :(".to_owned(),
|
||||
));
|
||||
};
|
||||
|
||||
Err(Error::InvalidData(
|
||||
"iCalendar component type not supported :(".to_owned(),
|
||||
))
|
||||
Ok(Self {
|
||||
data,
|
||||
properties: cal.properties,
|
||||
ics,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_data(&self) -> &CalendarObjectComponent {
|
||||
@@ -136,21 +125,22 @@ impl CalendarObject {
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> &str {
|
||||
&self.id
|
||||
match &self.data {
|
||||
CalendarObjectComponent::Todo(todo) => todo.0.get_uid(),
|
||||
CalendarObjectComponent::Event(event) => event.event.get_uid(),
|
||||
CalendarObjectComponent::Journal(journal) => journal.0.get_uid(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_etag(&self) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&self.id);
|
||||
hasher.update(self.get_id());
|
||||
hasher.update(self.get_ics());
|
||||
format!("\"{:x}\"", hasher.finalize())
|
||||
}
|
||||
|
||||
pub fn get_ics(&self) -> &str {
|
||||
match &self.data {
|
||||
CalendarObjectComponent::Todo(todo) => &todo.ics,
|
||||
CalendarObjectComponent::Event(event) => &event.ics,
|
||||
CalendarObjectComponent::Journal(journal) => &journal.ics,
|
||||
}
|
||||
&self.ics
|
||||
}
|
||||
|
||||
pub fn get_component_name(&self) -> &str {
|
||||
@@ -194,8 +184,11 @@ impl CalendarObject {
|
||||
// Only events can be expanded
|
||||
match &self.data {
|
||||
CalendarObjectComponent::Event(event) => {
|
||||
let mut cal = self.cal.clone();
|
||||
cal.events = event.expand_recurrence(start, end)?;
|
||||
let cal = IcalCalendar {
|
||||
properties: self.properties.clone(),
|
||||
events: event.expand_recurrence(start, end)?,
|
||||
..Default::default()
|
||||
};
|
||||
Ok(cal.generate())
|
||||
}
|
||||
_ => Ok(self.get_ics().to_string()),
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use derive_more::From;
|
||||
use ical::parser::ical::component::IcalTodo;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TodoObject {
|
||||
pub todo: IcalTodo,
|
||||
pub(crate) ics: String,
|
||||
}
|
||||
#[derive(Debug, Clone, From)]
|
||||
pub struct TodoObject(pub IcalTodo);
|
||||
|
||||
@@ -22,7 +22,17 @@ impl TryFrom<CalendarObjectRow> for CalendarObject {
|
||||
type Error = rustical_store::Error;
|
||||
|
||||
fn try_from(value: CalendarObjectRow) -> Result<Self, Self::Error> {
|
||||
Ok(CalendarObject::from_ics(value.id, value.ics)?)
|
||||
let object = CalendarObject::from_ics(value.ics)?;
|
||||
if object.get_id() != value.id {
|
||||
return Err(rustical_store::Error::IcalError(
|
||||
rustical_ical::Error::InvalidData(format!(
|
||||
"object_id={} and UID={} don't match",
|
||||
object.get_id(),
|
||||
value.id
|
||||
)),
|
||||
));
|
||||
}
|
||||
Ok(object)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user