mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-14 16:32:29 +00:00
Move events into their own file
This commit is contained in:
@@ -4,7 +4,8 @@ use anyhow::{anyhow, Result};
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use rustical_auth::AuthInfo;
|
use rustical_auth::AuthInfo;
|
||||||
use rustical_dav::resource::Resource;
|
use rustical_dav::resource::Resource;
|
||||||
use rustical_store::calendar::{CalendarStore, Event};
|
use rustical_store::calendar::CalendarStore;
|
||||||
|
use rustical_store::event::Event;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ use rustical_auth::{AuthInfoExtractor, CheckAuthentication};
|
|||||||
use rustical_dav::namespace::Namespace;
|
use rustical_dav::namespace::Namespace;
|
||||||
use rustical_dav::resource::HandlePropfind;
|
use rustical_dav::resource::HandlePropfind;
|
||||||
use rustical_dav::xml_snippets::generate_multistatus;
|
use rustical_dav::xml_snippets::generate_multistatus;
|
||||||
use rustical_store::calendar::{Calendar, CalendarStore, Event};
|
use rustical_store::calendar::{Calendar, CalendarStore};
|
||||||
|
use rustical_store::event::Event;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
|||||||
@@ -1,136 +1,7 @@
|
|||||||
use std::io::BufReader;
|
use crate::event::Event;
|
||||||
|
use anyhow::Result;
|
||||||
use crate::timestamps::{parse_datetime, parse_duration};
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use chrono::{Duration, NaiveDateTime, Timelike};
|
|
||||||
use ical::{
|
|
||||||
generator::{Emitter, IcalCalendar},
|
|
||||||
parser::Component,
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Event {
|
|
||||||
uid: String,
|
|
||||||
cal: IcalCalendar,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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> {
|
|
||||||
let mut parser = ical::IcalParser::new(BufReader::new(ics.as_bytes()));
|
|
||||||
let cal = parser.next().ok_or(anyhow!("no calendar :("))??;
|
|
||||||
if parser.next().is_some() {
|
|
||||||
return Err(anyhow!("multiple calendars!"));
|
|
||||||
}
|
|
||||||
if cal.events.len() != 1 {
|
|
||||||
return Err(anyhow!("multiple or no events"));
|
|
||||||
}
|
|
||||||
let event = Self { uid, cal };
|
|
||||||
// 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) -> String {
|
|
||||||
self.cal.generate()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_first_occurence(&self) -> Result<NaiveDateTime> {
|
|
||||||
// This is safe since we enforce the event's existance in the constructor
|
|
||||||
let event = &self.cal.events.get(0).unwrap();
|
|
||||||
let dtstart = event
|
|
||||||
.get_property("DTSTART")
|
|
||||||
.ok_or(anyhow!("DTSTART property missing!"))?
|
|
||||||
.value
|
|
||||||
.to_owned()
|
|
||||||
.ok_or(anyhow!("DTSTART property has no value!"))?;
|
|
||||||
parse_datetime(&dtstart)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_last_occurence(&self) -> Result<NaiveDateTime> {
|
|
||||||
// This is safe since we enforce the event's existance in the constructor
|
|
||||||
let event = &self.cal.events.get(0).unwrap();
|
|
||||||
|
|
||||||
if 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") {
|
|
||||||
let dtend = dtend_prop
|
|
||||||
.value
|
|
||||||
.to_owned()
|
|
||||||
.ok_or(anyhow!("DTEND property has no value!"))?;
|
|
||||||
return parse_datetime(&dtend);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(dtend_prop) = event.get_property("DURATION") {
|
|
||||||
let duration = dtend_prop
|
|
||||||
.value
|
|
||||||
.to_owned()
|
|
||||||
.ok_or(anyhow!("DURATION property has no value!"))?;
|
|
||||||
let dtstart = self.get_first_occurence()?;
|
|
||||||
return Ok(dtstart + parse_duration(&duration)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
let dtstart = self.get_first_occurence()?;
|
|
||||||
if dtstart.num_seconds_from_midnight() == 0 {
|
|
||||||
// no explicit time given => whole-day event
|
|
||||||
return Ok(dtstart + Duration::days(1));
|
|
||||||
};
|
|
||||||
|
|
||||||
Err(anyhow!("help, couldn't determine any last occurence"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
|
||||||
pub struct Calendar {
|
pub struct Calendar {
|
||||||
|
|||||||
131
crates/store/src/event.rs
Normal file
131
crates/store/src/event.rs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
use crate::timestamps::{parse_datetime, parse_duration};
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use chrono::{Duration, NaiveDateTime, Timelike};
|
||||||
|
use ical::{
|
||||||
|
generator::{Emitter, IcalCalendar},
|
||||||
|
parser::Component,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
use std::io::BufReader;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Event {
|
||||||
|
uid: String,
|
||||||
|
cal: IcalCalendar,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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> {
|
||||||
|
let mut parser = ical::IcalParser::new(BufReader::new(ics.as_bytes()));
|
||||||
|
let cal = parser.next().ok_or(anyhow!("no calendar :("))??;
|
||||||
|
if parser.next().is_some() {
|
||||||
|
return Err(anyhow!("multiple calendars!"));
|
||||||
|
}
|
||||||
|
if cal.events.len() != 1 {
|
||||||
|
return Err(anyhow!("multiple or no events"));
|
||||||
|
}
|
||||||
|
let event = Self { uid, cal };
|
||||||
|
// 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) -> String {
|
||||||
|
self.cal.generate()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_first_occurence(&self) -> Result<NaiveDateTime> {
|
||||||
|
// This is safe since we enforce the event's existance in the constructor
|
||||||
|
let event = self.cal.events.get(0).unwrap();
|
||||||
|
let dtstart = event
|
||||||
|
.get_property("DTSTART")
|
||||||
|
.ok_or(anyhow!("DTSTART property missing!"))?
|
||||||
|
.value
|
||||||
|
.to_owned()
|
||||||
|
.ok_or(anyhow!("DTSTART property has no value!"))?;
|
||||||
|
parse_datetime(&dtstart)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_last_occurence(&self) -> Result<NaiveDateTime> {
|
||||||
|
// This is safe since we enforce the event's existance in the constructor
|
||||||
|
let event = self.cal.events.get(0).unwrap();
|
||||||
|
|
||||||
|
if 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") {
|
||||||
|
let dtend = dtend_prop
|
||||||
|
.value
|
||||||
|
.to_owned()
|
||||||
|
.ok_or(anyhow!("DTEND property has no value!"))?;
|
||||||
|
return parse_datetime(&dtend);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(dtend_prop) = event.get_property("DURATION") {
|
||||||
|
let duration = dtend_prop
|
||||||
|
.value
|
||||||
|
.to_owned()
|
||||||
|
.ok_or(anyhow!("DURATION property has no value!"))?;
|
||||||
|
let dtstart = self.get_first_occurence()?;
|
||||||
|
return Ok(dtstart + parse_duration(&duration)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let dtstart = self.get_first_occurence()?;
|
||||||
|
if dtstart.num_seconds_from_midnight() == 0 {
|
||||||
|
// no explicit time given => whole-day event
|
||||||
|
return Ok(dtstart + Duration::days(1));
|
||||||
|
};
|
||||||
|
|
||||||
|
Err(anyhow!("help, couldn't determine any last occurence"))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
pub mod calendar;
|
pub mod calendar;
|
||||||
|
pub mod event;
|
||||||
pub mod timestamps;
|
pub mod timestamps;
|
||||||
pub mod toml_store;
|
pub mod toml_store;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::calendar::{Calendar, CalendarStore, Event};
|
use crate::calendar::{Calendar, CalendarStore};
|
||||||
|
use crate::event::Event;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|||||||
Reference in New Issue
Block a user