Migrate from Event type to CalendarObject

This is preparation to support other calendar components like VTODO and
VJOURNAL
This commit is contained in:
Lennart
2024-09-30 19:35:54 +02:00
parent 41d68f9ae0
commit b3a7806139
14 changed files with 229 additions and 161 deletions

View File

@@ -1,96 +1,21 @@
use crate::{
timestamp::{parse_duration, CalDateTime},
Error,
};
use std::str::FromStr;
use anyhow::{anyhow, Result};
use chrono::Duration;
use ical::parser::{ical::component::IcalCalendar, Component};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::{io::BufReader, str::FromStr};
use ical::{generator::IcalEvent, parser::Component};
use crate::timestamp::{parse_duration, CalDateTime};
#[derive(Debug, Clone)]
pub struct Event {
uid: String,
ics: String,
cal: IcalCalendar,
pub struct EventObject {
pub(crate) event: IcalEvent,
}
// Custom implementation for Event (de)serialization
impl<'de> Deserialize<'de> for Event {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct Inner {
uid: String,
ics: String,
}
let Inner { uid, ics } = Inner::deserialize(deserializer)?;
Self::from_ics(uid, ics).map_err(serde::de::Error::custom)
}
}
impl Serialize for Event {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
#[derive(Serialize)]
struct Inner {
uid: String,
ics: String,
}
Inner::serialize(
&Inner {
uid: self.get_uid().to_string(),
ics: self.get_ics().to_string(),
},
serializer,
)
}
}
impl Event {
// https://datatracker.ietf.org/doc/html/rfc4791#section-4.1
// MUST NOT contain more than one calendar objects (VEVENT, VTODO, VJOURNAL)
pub fn from_ics(uid: 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::InvalidIcs(
"multiple calendars, only one allowed".to_owned(),
));
}
if cal.events.len() != 1 {
return Err(Error::InvalidIcs(
"ics calendar must contain exactly one event".to_owned(),
));
}
let event = Self { uid, cal, ics };
// Run getters now to validate the input and ensure that they'll work later on
event.get_first_occurence()?;
event.get_last_occurence()?;
Ok(event)
}
pub fn get_uid(&self) -> &str {
&self.uid
}
pub fn get_etag(&self) -> String {
let mut hasher = Sha256::new();
hasher.update(&self.uid);
hasher.update(self.get_ics());
format!("{:x}", hasher.finalize())
}
pub fn get_ics(&self) -> &str {
&self.ics
}
impl EventObject {
pub fn get_first_occurence(&self) -> Result<CalDateTime> {
// This is safe since we enforce the event's existance in the constructor
let event = self.cal.events.first().unwrap();
let dtstart = event
let dtstart = self
.event
.get_property("DTSTART")
.ok_or(anyhow!("DTSTART property missing!"))?
.value
@@ -101,14 +26,12 @@ impl Event {
pub fn get_last_occurence(&self) -> Result<CalDateTime> {
// This is safe since we enforce the event's existence in the constructor
let event = self.cal.events.first().unwrap();
if event.get_property("RRULE").is_some() {
if self.event.get_property("RRULE").is_some() {
// TODO: understand recurrence rules
return Err(anyhow!("event is recurring, we cannot handle that yet"));
}
if let Some(dtend_prop) = event.get_property("DTEND") {
if let Some(dtend_prop) = self.event.get_property("DTEND") {
let dtend = dtend_prop
.value
.to_owned()
@@ -116,7 +39,7 @@ impl Event {
return Ok(CalDateTime::from_str(&dtend)?);
}
if let Some(dtend_prop) = event.get_property("DURATION") {
if let Some(dtend_prop) = self.event.get_property("DURATION") {
let duration = dtend_prop
.value
.to_owned()

View File

@@ -1,5 +1,6 @@
pub mod calendar;
pub mod event;
pub mod object;
pub use calendar::Calendar;
pub use event::Event;
pub use object::CalendarObject;

View File

@@ -0,0 +1,129 @@
use super::event::EventObject;
use crate::Error;
use anyhow::Result;
use ical::parser::ical::component::IcalTodo;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::io::BufReader;
#[derive(Debug, Clone)]
// specified in https://datatracker.ietf.org/doc/html/rfc5545#section-3.6
pub enum CalendarObjectType {
Event,
Journal,
Todo,
}
#[derive(Debug, Clone)]
pub struct TodoObject {
todo: IcalTodo,
}
#[derive(Debug, Clone)]
pub enum CalendarObjectComponent {
Event(EventObject),
Todo(TodoObject),
}
#[derive(Debug, Clone)]
pub struct CalendarObject {
uid: String,
ics: String,
data: CalendarObjectComponent,
}
// Custom implementation for CalendarObject (de)serialization
impl<'de> Deserialize<'de> for CalendarObject {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct Inner {
uid: String,
ics: String,
}
let Inner { uid, ics } = Inner::deserialize(deserializer)?;
Self::from_ics(uid, ics).map_err(serde::de::Error::custom)
}
}
impl Serialize for CalendarObject {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
#[derive(Serialize)]
struct Inner {
uid: String,
ics: String,
}
Inner::serialize(
&Inner {
uid: self.get_uid().to_string(),
ics: self.get_ics().to_string(),
},
serializer,
)
}
}
impl CalendarObject {
pub fn from_ics(uid: 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::InvalidIcs(
"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::InvalidIcs(
"iCalendar object is only allowed to have exactly one component".to_owned(),
));
}
if let Some(event) = cal.events.first() {
return Ok(CalendarObject {
uid,
ics,
data: CalendarObjectComponent::Event(EventObject {
event: event.clone(),
}),
});
}
if let Some(todo) = cal.todos.first() {
return Ok(CalendarObject {
uid,
ics,
data: CalendarObjectComponent::Todo(TodoObject { todo: todo.clone() }),
});
}
Err(Error::InvalidIcs(
"iCalendar component type not supported :(".to_owned(),
))
}
pub fn get_uid(&self) -> &str {
&self.uid
}
pub fn get_etag(&self) -> String {
// TODO: Probably save it in the database so we don't have to recompute every time?
let mut hasher = Sha256::new();
hasher.update(&self.uid);
hasher.update(self.get_ics());
format!("{:x}", hasher.finalize())
}
pub fn get_ics(&self) -> &str {
&self.ics
}
}

View File

@@ -1,5 +1,5 @@
use crate::model::object::CalendarObject;
use crate::model::Calendar;
use crate::model::Event;
use crate::{CalendarStore, Error};
use anyhow::Result;
use async_trait::async_trait;
@@ -19,16 +19,16 @@ impl SqliteCalendarStore {
}
#[derive(Debug, Clone)]
struct EventRow {
struct CalendarObjectRow {
uid: String,
ics: String,
}
impl TryFrom<EventRow> for Event {
impl TryFrom<CalendarObjectRow> for CalendarObject {
type Error = Error;
fn try_from(value: EventRow) -> Result<Self, Error> {
Event::from_ics(value.uid, value.ics)
fn try_from(value: CalendarObjectRow) -> Result<Self, Error> {
CalendarObject::from_ics(value.uid, value.ics)
}
}
@@ -186,9 +186,9 @@ impl CalendarStore for SqliteCalendarStore {
Ok(())
}
async fn get_events(&self, principal: &str, cid: &str) -> Result<Vec<Event>, Error> {
async fn get_objects(&self, principal: &str, cid: &str) -> Result<Vec<CalendarObject>, Error> {
sqlx::query_as!(
EventRow,
CalendarObjectRow,
"SELECT uid, ics FROM events WHERE principal = ? AND cid = ? AND deleted_at IS NULL",
principal,
cid
@@ -200,9 +200,14 @@ impl CalendarStore for SqliteCalendarStore {
.collect()
}
async fn get_event(&self, principal: &str, cid: &str, uid: &str) -> Result<Event, Error> {
async fn get_object(
&self,
principal: &str,
cid: &str,
uid: &str,
) -> Result<CalendarObject, Error> {
let event = sqlx::query_as!(
EventRow,
CalendarObjectRow,
"SELECT uid, ics FROM events WHERE (principal, cid, uid) = (?, ?, ?)",
principal,
cid,
@@ -214,7 +219,7 @@ impl CalendarStore for SqliteCalendarStore {
Ok(event)
}
async fn put_event(
async fn put_object(
&mut self,
principal: String,
cid: String,
@@ -224,7 +229,7 @@ impl CalendarStore for SqliteCalendarStore {
let mut tx = self.db.begin().await?;
// input validation
Event::from_ics(uid.to_owned(), ics.to_owned())?;
CalendarObject::from_ics(uid.to_owned(), ics.to_owned())?;
sqlx::query!(
"REPLACE INTO events (principal, cid, uid, ics) VALUES (?, ?, ?, ?)",
principal,
@@ -247,7 +252,7 @@ impl CalendarStore for SqliteCalendarStore {
Ok(())
}
async fn delete_event(
async fn delete_object(
&mut self,
principal: &str,
cid: &str,
@@ -285,7 +290,7 @@ impl CalendarStore for SqliteCalendarStore {
Ok(())
}
async fn restore_event(&mut self, principal: &str, cid: &str, uid: &str) -> Result<(), Error> {
async fn restore_object(&mut self, principal: &str, cid: &str, uid: &str) -> Result<(), Error> {
let mut tx = self.db.begin().await?;
sqlx::query!(
@@ -314,7 +319,7 @@ impl CalendarStore for SqliteCalendarStore {
principal: &str,
cid: &str,
synctoken: i64,
) -> Result<(Vec<Event>, Vec<String>, i64), Error> {
) -> Result<(Vec<CalendarObject>, Vec<String>, i64), Error> {
struct Row {
uid: String,
synctoken: i64,
@@ -340,7 +345,7 @@ impl CalendarStore for SqliteCalendarStore {
.unwrap_or(0);
for Row { uid, .. } in changes {
match self.get_event(principal, cid, &uid).await {
match self.get_object(principal, cid, &uid).await {
Ok(event) => events.push(event),
Err(Error::NotFound) => deleted_events.push(uid),
Err(err) => return Err(err),

View File

@@ -2,7 +2,8 @@ use anyhow::Result;
use async_trait::async_trait;
use crate::error::Error;
use crate::model::{Calendar, Event};
use crate::model::object::CalendarObject;
use crate::model::Calendar;
#[async_trait]
pub trait CalendarStore: Send + Sync + 'static {
@@ -29,23 +30,28 @@ pub trait CalendarStore: Send + Sync + 'static {
principal: &str,
cid: &str,
synctoken: i64,
) -> Result<(Vec<Event>, Vec<String>, i64), Error>;
) -> Result<(Vec<CalendarObject>, Vec<String>, i64), Error>;
async fn get_events(&self, principal: &str, cid: &str) -> Result<Vec<Event>, Error>;
async fn get_event(&self, principal: &str, cid: &str, uid: &str) -> Result<Event, Error>;
async fn put_event(
async fn get_objects(&self, principal: &str, cid: &str) -> Result<Vec<CalendarObject>, Error>;
async fn get_object(
&self,
principal: &str,
cid: &str,
uid: &str,
) -> Result<CalendarObject, Error>;
async fn put_object(
&mut self,
principal: String,
cid: String,
uid: String,
ics: String,
) -> Result<(), Error>;
async fn delete_event(
async fn delete_object(
&mut self,
principal: &str,
cid: &str,
uid: &str,
use_trashbin: bool,
) -> Result<(), Error>;
async fn restore_event(&mut self, principal: &str, cid: &str, uid: &str) -> Result<(), Error>;
async fn restore_object(&mut self, principal: &str, cid: &str, uid: &str) -> Result<(), Error>;
}

View File

@@ -37,7 +37,7 @@ async fn test_create_event<CS: CalendarStore>(mut store: CS) {
.unwrap();
store
.put_event(
.put_object(
"testuser".to_owned(),
"test".to_owned(),
"asd".to_owned(),
@@ -46,7 +46,7 @@ async fn test_create_event<CS: CalendarStore>(mut store: CS) {
.await
.unwrap();
let event = store.get_event("testuser", "test", "asd").await.unwrap();
let event = store.get_object("testuser", "test", "asd").await.unwrap();
assert_eq!(event.get_ics(), EVENT);
assert_eq!(event.get_uid(), "asd");
}