mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 18:12:27 +00:00
Move ical-related stuff to rustical_ical crate
This commit is contained in:
@@ -1,160 +0,0 @@
|
||||
use crate::{CalendarObject, Error};
|
||||
use chrono::Datelike;
|
||||
use ical::parser::{
|
||||
Component,
|
||||
vcard::{self, component::VcardContact},
|
||||
};
|
||||
use rustical_ical::{CalDateTime, LOCAL_DATE};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{collections::HashMap, io::BufReader};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AddressObject {
|
||||
id: String,
|
||||
vcf: String,
|
||||
vcard: VcardContact,
|
||||
}
|
||||
|
||||
impl AddressObject {
|
||||
pub fn from_vcf(object_id: String, vcf: String) -> Result<Self, Error> {
|
||||
let mut parser = vcard::VcardParser::new(BufReader::new(vcf.as_bytes()));
|
||||
let vcard = parser.next().ok_or(Error::NotFound)??;
|
||||
if parser.next().is_some() {
|
||||
return Err(Error::InvalidData(
|
||||
"multiple vcards, only one allowed".to_owned(),
|
||||
));
|
||||
}
|
||||
Ok(Self {
|
||||
id: object_id,
|
||||
vcf,
|
||||
vcard,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn get_etag(&self) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&self.id);
|
||||
hasher.update(self.get_vcf());
|
||||
format!("{:x}", hasher.finalize())
|
||||
}
|
||||
|
||||
pub fn get_vcf(&self) -> &str {
|
||||
&self.vcf
|
||||
}
|
||||
|
||||
pub fn get_anniversary(&self) -> Option<CalDateTime> {
|
||||
let prop = self.vcard.get_property("ANNIVERSARY")?;
|
||||
CalDateTime::parse_prop(prop, &HashMap::default()).ok()
|
||||
}
|
||||
|
||||
pub fn get_birthday(&self) -> Option<CalDateTime> {
|
||||
let prop = self.vcard.get_property("BDAY")?;
|
||||
CalDateTime::parse_prop(prop, &HashMap::default()).ok()
|
||||
}
|
||||
|
||||
pub fn get_full_name(&self) -> Option<&String> {
|
||||
let prop = self.vcard.get_property("FN")?;
|
||||
prop.value.as_ref()
|
||||
}
|
||||
|
||||
pub fn get_anniversary_object(&self) -> Result<Option<CalendarObject>, Error> {
|
||||
Ok(if let Some(anniversary) = self.get_anniversary() {
|
||||
let fullname = if let Some(name) = self.get_full_name() {
|
||||
name
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
let anniversary = anniversary.date();
|
||||
let year = anniversary.year();
|
||||
let anniversary_start = anniversary.format(LOCAL_DATE);
|
||||
let anniversary_end = anniversary
|
||||
.succ_opt()
|
||||
.unwrap_or(anniversary)
|
||||
.format(LOCAL_DATE);
|
||||
let uid = format!("{}-anniversary", self.get_id());
|
||||
|
||||
Some(CalendarObject::from_ics(
|
||||
uid.clone(),
|
||||
format!(
|
||||
r#"BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
CALSCALE:GREGORIAN
|
||||
PRODID:-//github.com/lennart-k/rustical birthday calendar//EN
|
||||
BEGIN:VEVENT
|
||||
DTSTART;VALUE=DATE:{anniversary_start}
|
||||
DTEND;VALUE=DATE:{anniversary_end}
|
||||
UID:{uid}
|
||||
RRULE:FREQ=YEARLY
|
||||
SUMMARY:💍 {fullname} ({year})
|
||||
TRANSP:TRANSPARENT
|
||||
BEGIN:VALARM
|
||||
TRIGGER;VALUE=DURATION:-PT0M
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:💍 {fullname} ({year})
|
||||
END:VALARM
|
||||
END:VEVENT
|
||||
END:VCALENDAR"#,
|
||||
),
|
||||
)?)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_birthday_object(&self) -> Result<Option<CalendarObject>, Error> {
|
||||
Ok(if let Some(birthday) = self.get_birthday() {
|
||||
let fullname = if let Some(name) = self.get_full_name() {
|
||||
name
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
let birthday = birthday.date();
|
||||
let year = birthday.year();
|
||||
let birthday_start = birthday.format(LOCAL_DATE);
|
||||
let birthday_end = birthday.succ_opt().unwrap_or(birthday).format(LOCAL_DATE);
|
||||
let uid = format!("{}-birthday", self.get_id());
|
||||
|
||||
Some(CalendarObject::from_ics(
|
||||
uid.clone(),
|
||||
format!(
|
||||
r#"BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
CALSCALE:GREGORIAN
|
||||
PRODID:-//github.com/lennart-k/rustical birthday calendar//EN
|
||||
BEGIN:VEVENT
|
||||
DTSTART;VALUE=DATE:{birthday_start}
|
||||
DTEND;VALUE=DATE:{birthday_end}
|
||||
UID:{uid}
|
||||
RRULE:FREQ=YEARLY
|
||||
SUMMARY:🎂 {fullname} ({year})
|
||||
TRANSP:TRANSPARENT
|
||||
BEGIN:VALARM
|
||||
TRIGGER;VALUE=DURATION:-PT0M
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:🎂 {fullname} ({year})
|
||||
END:VALARM
|
||||
END:VEVENT
|
||||
END:VCALENDAR"#,
|
||||
),
|
||||
)?)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
/// Get significant dates associated with this address object
|
||||
pub fn get_significant_dates(&self) -> Result<HashMap<&'static str, CalendarObject>, Error> {
|
||||
let mut out = HashMap::new();
|
||||
if let Some(birthday) = self.get_birthday_object()? {
|
||||
out.insert("birthday", birthday);
|
||||
}
|
||||
if let Some(anniversary) = self.get_anniversary_object()? {
|
||||
out.insert("anniversary", anniversary);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
pub mod address_object;
|
||||
pub mod addressbook;
|
||||
|
||||
pub use address_object::*;
|
||||
pub use addressbook::*;
|
||||
@@ -1,8 +1,6 @@
|
||||
use crate::{
|
||||
Error,
|
||||
addressbook::{AddressObject, Addressbook},
|
||||
};
|
||||
use crate::{Error, addressbook::Addressbook};
|
||||
use async_trait::async_trait;
|
||||
use rustical_ical::AddressObject;
|
||||
|
||||
#[async_trait]
|
||||
pub trait AddressbookStore: Send + Sync + 'static {
|
||||
|
||||
@@ -38,7 +38,7 @@ impl TryFrom<&str> for PrincipalType {
|
||||
"ROOM" => Self::Room,
|
||||
"UNKNOWN" => Self::Unknown,
|
||||
_ => {
|
||||
return Err(crate::Error::InvalidData(
|
||||
return Err(crate::Error::InvalidPrincipalType(
|
||||
"Invalid principal type".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::CalendarObjectType;
|
||||
use crate::synctoken::format_synctoken;
|
||||
use chrono::NaiveDateTime;
|
||||
use rustical_ical::CalendarObjectType;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize)]
|
||||
@@ -1,212 +0,0 @@
|
||||
use crate::Error;
|
||||
use chrono::{DateTime, Duration};
|
||||
use ical::{
|
||||
generator::IcalEvent,
|
||||
parser::{Component, ical::component::IcalTimeZone},
|
||||
property::Property,
|
||||
};
|
||||
use rrule::{RRule, RRuleSet};
|
||||
use rustical_ical::{CalDateTime, ComponentMut, parse_duration};
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EventObject {
|
||||
pub(crate) event: IcalEvent,
|
||||
pub(crate) timezones: HashMap<String, IcalTimeZone>,
|
||||
pub(crate) ics: String,
|
||||
}
|
||||
|
||||
impl EventObject {
|
||||
pub fn get_first_occurence(&self) -> Result<Option<CalDateTime>, Error> {
|
||||
if let Some(dtstart) = self.event.get_property("DTSTART") {
|
||||
Ok(Some(CalDateTime::parse_prop(dtstart, &self.timezones)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_last_occurence(&self) -> Result<Option<CalDateTime>, Error> {
|
||||
if let Some(_rrule) = self.event.get_property("RRULE") {
|
||||
// TODO: understand recurrence rules
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if let Some(dtend) = self.event.get_property("DTEND") {
|
||||
return Ok(Some(CalDateTime::parse_prop(dtend, &self.timezones)?));
|
||||
};
|
||||
|
||||
let duration = self.get_duration()?.unwrap_or(Duration::days(1));
|
||||
|
||||
let first_occurence = self.get_first_occurence()?;
|
||||
Ok(first_occurence.map(|first_occurence| first_occurence + duration))
|
||||
}
|
||||
|
||||
pub fn get_duration(&self) -> Result<Option<Duration>, Error> {
|
||||
if let Some(Property {
|
||||
value: Some(duration),
|
||||
..
|
||||
}) = self.event.get_property("DURATION")
|
||||
{
|
||||
Ok(Some(parse_duration(duration)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recurrence_ruleset(&self) -> Result<Option<rrule::RRuleSet>, Error> {
|
||||
let dtstart: DateTime<rrule::Tz> = if let Some(dtstart) = self.get_first_occurence()? {
|
||||
dtstart
|
||||
.as_datetime()
|
||||
.with_timezone(&dtstart.timezone().into())
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let mut rrule_set = RRuleSet::new(dtstart);
|
||||
|
||||
for prop in &self.event.properties {
|
||||
rrule_set = match prop.name.as_str() {
|
||||
"RRULE" => {
|
||||
let rrule = RRule::from_str(prop.value.as_ref().ok_or(Error::RRuleError(
|
||||
rrule::ParseError::MissingDateGenerationRules.into(),
|
||||
))?)?
|
||||
.validate(dtstart)
|
||||
.unwrap();
|
||||
rrule_set.rrule(rrule)
|
||||
}
|
||||
"RDATE" => {
|
||||
let rdate = CalDateTime::parse_prop(prop, &self.timezones)?.into();
|
||||
rrule_set.rdate(rdate)
|
||||
}
|
||||
"EXDATE" => {
|
||||
let exdate = CalDateTime::parse_prop(prop, &self.timezones)?.into();
|
||||
rrule_set.exdate(exdate)
|
||||
}
|
||||
_ => rrule_set,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(rrule_set))
|
||||
}
|
||||
|
||||
pub fn expand_recurrence(&self) -> Result<Vec<IcalEvent>, Error> {
|
||||
if let Some(rrule_set) = self.recurrence_ruleset()? {
|
||||
let mut events = vec![];
|
||||
let dates = rrule_set.all(2048).dates;
|
||||
|
||||
for date in dates {
|
||||
let date = CalDateTime::from(date);
|
||||
let mut ev = self.event.clone();
|
||||
ev.remove_property("RRULE");
|
||||
ev.set_property(Property {
|
||||
name: "RECURRENCE-ID".to_string(),
|
||||
value: Some(date.format()),
|
||||
params: None,
|
||||
});
|
||||
ev.set_property(Property {
|
||||
name: "DTSTART".to_string(),
|
||||
value: Some(date.format()),
|
||||
params: None,
|
||||
});
|
||||
if let Some(duration) = self.get_duration()? {
|
||||
ev.set_property(Property {
|
||||
name: "DTEND".to_string(),
|
||||
value: Some((date + duration).format()),
|
||||
params: None,
|
||||
});
|
||||
}
|
||||
events.push(ev);
|
||||
}
|
||||
Ok(events)
|
||||
} else {
|
||||
Ok(vec![self.event.clone()])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::CalendarObject;
|
||||
use ical::generator::Emitter;
|
||||
|
||||
const ICS: &str = r#"BEGIN:VCALENDAR
|
||||
CALSCALE:GREGORIAN
|
||||
VERSION:2.0
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:Europe/Berlin
|
||||
X-LIC-LOCATION:Europe/Berlin
|
||||
END:VTIMEZONE
|
||||
|
||||
BEGIN:VEVENT
|
||||
UID:318ec6503573d9576818daf93dac07317058d95c
|
||||
DTSTAMP:20250502T132758Z
|
||||
DTSTART;TZID=Europe/Berlin:20250506T090000
|
||||
DTEND;TZID=Europe/Berlin:20250506T092500
|
||||
SEQUENCE:2
|
||||
SUMMARY:weekly stuff
|
||||
TRANSP:OPAQUE
|
||||
RRULE:FREQ=WEEKLY;COUNT=4;INTERVAL=2;BYDAY=TU,TH,SU
|
||||
END:VEVENT
|
||||
END:VCALENDAR"#;
|
||||
|
||||
const EXPANDED: [&str; 4] = [
|
||||
"BEGIN:VEVENT\r
|
||||
UID:318ec6503573d9576818daf93dac07317058d95c\r
|
||||
DTSTAMP:20250502T132758Z\r
|
||||
DTEND;TZID=Europe/Berlin:20250506T092500\r
|
||||
SEQUENCE:2\r
|
||||
SUMMARY:weekly stuff\r
|
||||
TRANSP:OPAQUE\r
|
||||
RECURRENCE-ID:20250506T090000\r
|
||||
DTSTART:20250506T090000\r
|
||||
END:VEVENT\r\n",
|
||||
"BEGIN:VEVENT\r
|
||||
UID:318ec6503573d9576818daf93dac07317058d95c\r
|
||||
DTSTAMP:20250502T132758Z\r
|
||||
DTEND;TZID=Europe/Berlin:20250506T092500\r
|
||||
SEQUENCE:2\r
|
||||
SUMMARY:weekly stuff\r
|
||||
TRANSP:OPAQUE\r
|
||||
RECURRENCE-ID:20250508T090000\r
|
||||
DTSTART:20250508T090000\r
|
||||
END:VEVENT\r\n",
|
||||
"BEGIN:VEVENT\r
|
||||
UID:318ec6503573d9576818daf93dac07317058d95c\r
|
||||
DTSTAMP:20250502T132758Z\r
|
||||
DTEND;TZID=Europe/Berlin:20250506T092500\r
|
||||
SEQUENCE:2\r
|
||||
SUMMARY:weekly stuff\r
|
||||
TRANSP:OPAQUE\r
|
||||
RECURRENCE-ID:20250511T090000\r
|
||||
DTSTART:20250511T090000\r
|
||||
END:VEVENT\r\n",
|
||||
"BEGIN:VEVENT\r
|
||||
UID:318ec6503573d9576818daf93dac07317058d95c\r
|
||||
DTSTAMP:20250502T132758Z\r
|
||||
DTEND;TZID=Europe/Berlin:20250506T092500\r
|
||||
SEQUENCE:2\r
|
||||
SUMMARY:weekly stuff\r
|
||||
TRANSP:OPAQUE\r
|
||||
RECURRENCE-ID:20250520T090000\r
|
||||
DTSTART:20250520T090000\r
|
||||
END:VEVENT\r\n",
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_expand_recurrence() {
|
||||
let event = CalendarObject::from_ics(
|
||||
"318ec6503573d9576818daf93dac07317058d95c".to_string(),
|
||||
ICS.to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
let event = event.event().unwrap();
|
||||
|
||||
let events: Vec<String> = event
|
||||
.expand_recurrence()
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|event| Emitter::generate(&event))
|
||||
.collect();
|
||||
assert_eq!(events.as_slice(), &EXPANDED);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
use ical::parser::ical::component::IcalJournal;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JournalObject {
|
||||
pub journal: IcalJournal,
|
||||
pub(crate) ics: String,
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
mod calendar;
|
||||
mod event;
|
||||
mod journal;
|
||||
mod object;
|
||||
mod todo;
|
||||
|
||||
pub use calendar::*;
|
||||
pub use event::*;
|
||||
pub use journal::*;
|
||||
pub use object::*;
|
||||
pub use todo::*;
|
||||
@@ -1,202 +0,0 @@
|
||||
use super::{EventObject, JournalObject, TodoObject};
|
||||
use crate::Error;
|
||||
use ical::{
|
||||
generator::{Emitter, IcalCalendar},
|
||||
parser::{Component, ical::component::IcalTimeZone},
|
||||
};
|
||||
use rustical_ical::CalDateTime;
|
||||
use serde::Serialize;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{collections::HashMap, io::BufReader};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
||||
// specified in https://datatracker.ietf.org/doc/html/rfc5545#section-3.6
|
||||
pub enum CalendarObjectType {
|
||||
Event = 0,
|
||||
Todo = 1,
|
||||
Journal = 2,
|
||||
}
|
||||
|
||||
impl CalendarObjectType {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
CalendarObjectType::Event => "VEVENT",
|
||||
CalendarObjectType::Todo => "VTODO",
|
||||
CalendarObjectType::Journal => "VJOURNAL",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl rustical_xml::ValueSerialize for CalendarObjectType {
|
||||
fn serialize(&self) -> String {
|
||||
self.as_str().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl rustical_xml::ValueDeserialize for CalendarObjectType {
|
||||
fn deserialize(val: &str) -> std::result::Result<Self, rustical_xml::XmlError> {
|
||||
match <String as rustical_xml::ValueDeserialize>::deserialize(val)?.as_str() {
|
||||
"VEVENT" => Ok(Self::Event),
|
||||
"VTODO" => Ok(Self::Todo),
|
||||
"VJOURNAL" => Ok(Self::Journal),
|
||||
_ => Err(rustical_xml::XmlError::InvalidValue(
|
||||
rustical_xml::ParseValueError::Other(format!(
|
||||
"Invalid value '{}', must be VEVENT, VTODO, or VJOURNAL",
|
||||
val
|
||||
)),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CalendarObjectComponent {
|
||||
Event(EventObject),
|
||||
Todo(TodoObject),
|
||||
Journal(JournalObject),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CalendarObject {
|
||||
id: String,
|
||||
data: CalendarObjectComponent,
|
||||
cal: IcalCalendar,
|
||||
}
|
||||
|
||||
impl CalendarObject {
|
||||
pub fn from_ics(object_id: String, ics: String) -> Result<Self, Error> {
|
||||
let mut parser = ical::IcalParser::new(BufReader::new(ics.as_bytes()));
|
||||
let cal = parser.next().ok_or(Error::NotFound)??;
|
||||
if parser.next().is_some() {
|
||||
return Err(Error::InvalidData(
|
||||
"multiple calendars, only one allowed".to_owned(),
|
||||
));
|
||||
}
|
||||
if cal.events.len()
|
||||
+ cal.alarms.len()
|
||||
+ cal.todos.len()
|
||||
+ cal.journals.len()
|
||||
+ cal.free_busys.len()
|
||||
!= 1
|
||||
{
|
||||
// https://datatracker.ietf.org/doc/html/rfc4791#section-4.1
|
||||
return Err(Error::InvalidData(
|
||||
"iCalendar object is only allowed to have exactly one component".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let timezones: HashMap<String, IcalTimeZone> = cal
|
||||
.timezones
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter_map(|timezone| {
|
||||
let timezone_prop = timezone.get_property("TZID")?.to_owned();
|
||||
let tzid = timezone_prop.value?;
|
||||
Some((tzid, timezone))
|
||||
})
|
||||
.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,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
Err(Error::InvalidData(
|
||||
"iCalendar component type not supported :(".to_owned(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
pub fn get_etag(&self) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&self.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,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_component_name(&self) -> &str {
|
||||
match self.data {
|
||||
CalendarObjectComponent::Todo(_) => "VTODO",
|
||||
CalendarObjectComponent::Event(_) => "VEVENT",
|
||||
CalendarObjectComponent::Journal(_) => "VJOURNAL",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_object_type(&self) -> CalendarObjectType {
|
||||
match self.data {
|
||||
CalendarObjectComponent::Todo(_) => CalendarObjectType::Todo,
|
||||
CalendarObjectComponent::Event(_) => CalendarObjectType::Event,
|
||||
CalendarObjectComponent::Journal(_) => CalendarObjectType::Journal,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_first_occurence(&self) -> Result<Option<CalDateTime>, Error> {
|
||||
match &self.data {
|
||||
CalendarObjectComponent::Event(event) => event.get_first_occurence(),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_last_occurence(&self) -> Result<Option<CalDateTime>, Error> {
|
||||
match &self.data {
|
||||
CalendarObjectComponent::Event(event) => event.get_last_occurence(),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn event(&self) -> Option<&EventObject> {
|
||||
match &self.data {
|
||||
CalendarObjectComponent::Event(event) => Some(event),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expand_recurrence(&self) -> Result<String, Error> {
|
||||
// Only events can be expanded
|
||||
match &self.data {
|
||||
CalendarObjectComponent::Event(event) => {
|
||||
let mut cal = self.cal.clone();
|
||||
cal.events = event.expand_recurrence()?;
|
||||
Ok(cal.generate())
|
||||
}
|
||||
_ => Ok(self.get_ics().to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
use ical::parser::ical::component::IcalTodo;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TodoObject {
|
||||
pub todo: IcalTodo,
|
||||
pub(crate) ics: String,
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::calendar::{Calendar, CalendarObject};
|
||||
use crate::error::Error;
|
||||
use crate::{Calendar, error::Error};
|
||||
use async_trait::async_trait;
|
||||
use chrono::NaiveDate;
|
||||
use rustical_ical::CalendarObject;
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct CalendarQuery {
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
AddressObject, Addressbook, AddressbookStore, Calendar, CalendarObject, CalendarStore, Error,
|
||||
calendar::CalendarObjectType,
|
||||
};
|
||||
use crate::{Addressbook, AddressbookStore, Calendar, CalendarStore, Error};
|
||||
use async_trait::async_trait;
|
||||
use derive_more::derive::Constructor;
|
||||
use rustical_ical::{AddressObject, CalendarObject, CalendarObjectType};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
#[derive(Constructor, Clone)]
|
||||
pub struct ContactBirthdayStore<AS: AddressbookStore>(Arc<AS>);
|
||||
@@ -85,7 +82,7 @@ impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> {
|
||||
) -> Result<(Vec<CalendarObject>, Vec<String>, i64), Error> {
|
||||
let (objects, deleted_objects, new_synctoken) =
|
||||
self.0.sync_changes(principal, cal_id, synctoken).await?;
|
||||
let objects: Result<Vec<Option<CalendarObject>>, Error> = objects
|
||||
let objects: Result<Vec<Option<CalendarObject>>, rustical_ical::Error> = objects
|
||||
.iter()
|
||||
.map(AddressObject::get_birthday_object)
|
||||
.collect();
|
||||
@@ -99,13 +96,13 @@ impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> {
|
||||
principal: &str,
|
||||
cal_id: &str,
|
||||
) -> Result<Vec<CalendarObject>, Error> {
|
||||
let objects: Result<Vec<HashMap<&'static str, CalendarObject>>, Error> = self
|
||||
.0
|
||||
.get_objects(principal, cal_id)
|
||||
.await?
|
||||
.iter()
|
||||
.map(AddressObject::get_significant_dates)
|
||||
.collect();
|
||||
let objects: Result<Vec<HashMap<&'static str, CalendarObject>>, rustical_ical::Error> =
|
||||
self.0
|
||||
.get_objects(principal, cal_id)
|
||||
.await?
|
||||
.iter()
|
||||
.map(AddressObject::get_significant_dates)
|
||||
.collect();
|
||||
let objects = objects?
|
||||
.into_iter()
|
||||
.flat_map(HashMap::into_values)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use actix_web::{ResponseError, http::StatusCode};
|
||||
use rustical_ical::CalDateTimeError;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
@@ -9,8 +8,8 @@ pub enum Error {
|
||||
#[error("Resource already exists and overwrite=false")]
|
||||
AlreadyExists,
|
||||
|
||||
#[error("Invalid ics/vcf input: {0}")]
|
||||
InvalidData(String),
|
||||
#[error("Invalid principal type: {0}")]
|
||||
InvalidPrincipalType(String),
|
||||
|
||||
#[error("Read-only")]
|
||||
ReadOnly,
|
||||
@@ -21,17 +20,11 @@ pub enum Error {
|
||||
#[error(transparent)]
|
||||
IO(#[from] std::io::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
ParserError(#[from] ical::parser::ParserError),
|
||||
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
CalDateTimeError(#[from] CalDateTimeError),
|
||||
|
||||
#[error(transparent)]
|
||||
RRuleError(#[from] rrule::RRuleError),
|
||||
IcalError(#[from] rustical_ical::Error),
|
||||
}
|
||||
|
||||
impl ResponseError for Error {
|
||||
@@ -39,8 +32,9 @@ impl ResponseError for Error {
|
||||
match self {
|
||||
Self::NotFound => StatusCode::NOT_FOUND,
|
||||
Self::AlreadyExists => StatusCode::CONFLICT,
|
||||
Self::InvalidData(_) => StatusCode::BAD_REQUEST,
|
||||
Self::ReadOnly => StatusCode::FORBIDDEN,
|
||||
Self::IcalError(err) => err.status_code(),
|
||||
Self::InvalidPrincipalType(_) => StatusCode::BAD_REQUEST,
|
||||
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ pub mod calendar_store;
|
||||
pub mod error;
|
||||
pub use error::Error;
|
||||
pub mod auth;
|
||||
pub mod calendar;
|
||||
mod calendar;
|
||||
mod contact_birthday_store;
|
||||
mod secret;
|
||||
mod subscription_store;
|
||||
@@ -16,8 +16,8 @@ pub use contact_birthday_store::ContactBirthdayStore;
|
||||
pub use secret::Secret;
|
||||
pub use subscription_store::*;
|
||||
|
||||
pub use addressbook::{AddressObject, Addressbook};
|
||||
pub use calendar::{Calendar, CalendarObject};
|
||||
pub use addressbook::Addressbook;
|
||||
pub use calendar::Calendar;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CollectionOperationType {
|
||||
|
||||
Reference in New Issue
Block a user