use crate::event::resource::EventFile; use crate::Error; use actix_web::{web::Data, HttpRequest}; use anyhow::anyhow; use async_trait::async_trait; use rustical_auth::AuthInfo; use rustical_dav::resource::{InvalidProperty, Resource, ResourceService}; use rustical_dav::xml::HrefElement; use rustical_store::calendar::Calendar; use rustical_store::CalendarStore; use serde::{Deserialize, Serialize}; use std::sync::Arc; use strum::{EnumString, VariantNames}; use tokio::sync::RwLock; use super::prop::{ Resourcetype, SupportedCalendarComponent, SupportedCalendarComponentSet, SupportedCalendarData, UserPrivilegeSet, }; pub struct CalendarResource { pub cal_store: Arc>, pub path: String, pub principal: String, pub calendar_id: String, } #[derive(EnumString, Debug, VariantNames, Clone)] #[strum(serialize_all = "kebab-case")] pub enum CalendarPropName { Resourcetype, CurrentUserPrincipal, Owner, Displayname, CalendarColor, CalendarDescription, CalendarTimezone, CalendarOrder, SupportedCalendarComponentSet, SupportedCalendarData, Getcontenttype, CurrentUserPrivilegeSet, MaxResourceSize, } #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub enum CalendarProp { Resourcetype(Resourcetype), CurrentUserPrincipal(HrefElement), Owner(HrefElement), Displayname(Option), #[serde(rename = "IC:calendar-color", alias = "calendar-color")] CalendarColor(Option), #[serde(rename = "C:calendar-description", alias = "calendar-description")] CalendarDescription(Option), #[serde(rename = "C:calendar-timezone", alias = "calendar-timezone")] CalendarTimezone(Option), #[serde(rename = "IC:calendar-order", alias = "calendar-order")] CalendarOrder(Option), #[serde( rename = "C:supported-calendar-component-set", alias = "supported-calendar-component-set" )] SupportedCalendarComponentSet(SupportedCalendarComponentSet), #[serde( rename = "C:supported-calendar-data", alias = "supported-calendar-data" )] SupportedCalendarData(SupportedCalendarData), Getcontenttype(String), MaxResourceSize(String), CurrentUserPrivilegeSet(UserPrivilegeSet), #[serde(other)] Invalid, } impl InvalidProperty for CalendarProp { fn invalid_property(&self) -> bool { matches!(self, Self::Invalid) } } #[derive(Clone, Debug)] pub struct CalendarFile { pub calendar: Calendar, pub principal: String, } impl Resource for CalendarFile { type PropName = CalendarPropName; type Prop = CalendarProp; type Error = Error; fn get_prop(&self, prefix: &str, prop: Self::PropName) -> Result { match prop { CalendarPropName::Resourcetype => { Ok(CalendarProp::Resourcetype(Resourcetype::default())) } CalendarPropName::CurrentUserPrincipal => Ok(CalendarProp::CurrentUserPrincipal( HrefElement::new(format!("{}/user/{}/", prefix, self.principal)), )), CalendarPropName::Owner => Ok(CalendarProp::Owner(HrefElement::new(format!( "{}/user/{}/", prefix, self.principal )))), CalendarPropName::Displayname => { Ok(CalendarProp::Displayname(self.calendar.displayname.clone())) } CalendarPropName::CalendarColor => { Ok(CalendarProp::CalendarColor(self.calendar.color.clone())) } CalendarPropName::CalendarDescription => Ok(CalendarProp::CalendarDescription( self.calendar.description.clone(), )), CalendarPropName::CalendarTimezone => Ok(CalendarProp::CalendarTimezone( self.calendar.timezone.clone(), )), CalendarPropName::CalendarOrder => Ok(CalendarProp::CalendarOrder( format!("{}", self.calendar.order).into(), )), CalendarPropName::SupportedCalendarComponentSet => Ok( CalendarProp::SupportedCalendarComponentSet(SupportedCalendarComponentSet { comp: vec![SupportedCalendarComponent { name: "VEVENT".to_owned(), }], }), ), CalendarPropName::SupportedCalendarData => Ok(CalendarProp::SupportedCalendarData( SupportedCalendarData::default(), )), CalendarPropName::Getcontenttype => Ok(CalendarProp::Getcontenttype( "text/calendar;charset=utf-8".to_owned(), )), CalendarPropName::MaxResourceSize => { Ok(CalendarProp::MaxResourceSize("10000000".to_owned())) } CalendarPropName::CurrentUserPrivilegeSet => Ok(CalendarProp::CurrentUserPrivilegeSet( UserPrivilegeSet::default(), )), } } fn set_prop(&mut self, prop: Self::Prop) -> Result<(), rustical_dav::Error> { match prop { CalendarProp::Resourcetype(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::CurrentUserPrincipal(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::Owner(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::Displayname(displayname) => { self.calendar.displayname = displayname; Ok(()) } CalendarProp::CalendarColor(color) => { self.calendar.color = color; Ok(()) } CalendarProp::CalendarDescription(description) => { self.calendar.description = description; Ok(()) } CalendarProp::CalendarTimezone(timezone) => { self.calendar.timezone = timezone; Ok(()) } CalendarProp::CalendarOrder(order) => { self.calendar.order = match order { Some(order) => order.parse().map_err(|_e| anyhow!("invalid order"))?, None => 0, }; Ok(()) } CalendarProp::SupportedCalendarComponentSet(_) => { Err(rustical_dav::Error::PropReadOnly) } CalendarProp::SupportedCalendarData(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::Getcontenttype(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::MaxResourceSize(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::CurrentUserPrivilegeSet(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::Invalid => Err(rustical_dav::Error::PropReadOnly), } } fn remove_prop(&mut self, prop: Self::PropName) -> Result<(), rustical_dav::Error> { match prop { CalendarPropName::Resourcetype => Err(rustical_dav::Error::PropReadOnly), CalendarPropName::CurrentUserPrincipal => Err(rustical_dav::Error::PropReadOnly), CalendarPropName::Owner => Err(rustical_dav::Error::PropReadOnly), CalendarPropName::Displayname => { self.calendar.displayname = None; Ok(()) } CalendarPropName::CalendarColor => { self.calendar.color = None; Ok(()) } CalendarPropName::CalendarDescription => { self.calendar.description = None; Ok(()) } CalendarPropName::CalendarTimezone => { self.calendar.timezone = None; Ok(()) } CalendarPropName::CalendarOrder => { self.calendar.order = 0; Ok(()) } CalendarPropName::SupportedCalendarComponentSet => { Err(rustical_dav::Error::PropReadOnly) } CalendarPropName::SupportedCalendarData => Err(rustical_dav::Error::PropReadOnly), CalendarPropName::Getcontenttype => Err(rustical_dav::Error::PropReadOnly), CalendarPropName::MaxResourceSize => Err(rustical_dav::Error::PropReadOnly), CalendarPropName::CurrentUserPrivilegeSet => Err(rustical_dav::Error::PropReadOnly), } } } #[async_trait(?Send)] impl ResourceService for CalendarResource { type MemberType = EventFile; type PathComponents = (String, String); // principal, calendar_id type File = CalendarFile; type Error = Error; async fn get_file(&self) -> Result { let calendar = self .cal_store .read() .await .get_calendar(&self.principal, &self.calendar_id) .await .map_err(|_e| Error::NotFound)?; Ok(CalendarFile { calendar, principal: self.principal.to_owned(), }) } async fn get_members( &self, _auth_info: AuthInfo, ) -> Result, Self::Error> { // As of now the calendar resource has no members since events are shown with REPORT Ok(self .cal_store .read() .await .get_events(&self.principal, &self.calendar_id) .await? .into_iter() .map(|event| { ( format!("{}/{}", self.path, &event.get_uid()), EventFile { event }, ) }) .collect()) } async fn new( req: &HttpRequest, auth_info: &AuthInfo, path_components: Self::PathComponents, ) -> Result { let cal_store = req .app_data::>>() .ok_or(anyhow!("no calendar store in app_data!"))? .clone() .into_inner(); Ok(Self { path: req.path().to_owned(), principal: auth_info.user_id.to_owned(), calendar_id: path_components.1, cal_store, }) } async fn save_file(&self, file: Self::File) -> Result<(), Self::Error> { self.cal_store .write() .await .update_calendar( self.principal.to_owned(), self.calendar_id.to_owned(), file.calendar, ) .await?; Ok(()) } async fn delete_file(&self, use_trashbin: bool) -> Result<(), Self::Error> { self.cal_store .write() .await .delete_calendar(&self.principal, &self.calendar_id, use_trashbin) .await?; Ok(()) } }