use actix_web::{http::StatusCode, HttpRequest}; use rustical_dav::{ resource::{CommonPropertiesProp, EitherProp, Resource}, xml::{multistatus::ResponseElement, MultistatusElement}, xml::{PropElement, PropfindType}, }; use rustical_store::{ auth::User, synctoken::{format_synctoken, parse_synctoken}, CalendarStore, }; use rustical_xml::{ValueDeserialize, ValueSerialize, XmlDeserialize}; use crate::{ calendar_object::resource::{CalendarObjectProp, CalendarObjectResource}, Error, }; #[derive(Clone, Debug, PartialEq)] pub(crate) enum SyncLevel { One, Infinity, } impl ValueDeserialize for SyncLevel { fn deserialize(val: &str) -> Result { Ok(match val { "1" => Self::One, "Infinity" => Self::Infinity, _ => { return Err(rustical_xml::XmlError::Other( "Invalid sync-level".to_owned(), )) } }) } } impl ValueSerialize for SyncLevel { fn serialize(&self) -> String { match self { SyncLevel::One => "1", SyncLevel::Infinity => "Infinity", } .to_owned() } } #[derive(XmlDeserialize, Clone, Debug, PartialEq)] #[allow(dead_code)] // // // pub(crate) struct SyncCollectionRequest { pub(crate) sync_token: String, pub(crate) sync_level: SyncLevel, pub(crate) timezone: Option, #[xml(ty = "untagged")] pub prop: PropfindType, pub(crate) limit: Option, } pub async fn handle_sync_collection( sync_collection: SyncCollectionRequest, req: HttpRequest, user: &User, principal: &str, cal_id: &str, cal_store: &C, ) -> Result, String>, Error> { let props = match sync_collection.prop { PropfindType::Allprop => { vec!["allprop".to_owned()] } PropfindType::Propname => { vec!["propname".to_owned()] } PropfindType::Prop(PropElement(prop_tags)) => { prop_tags.into_iter().map(|propname| propname.0).collect() } }; let props: Vec<&str> = props.iter().map(String::as_str).collect(); let old_synctoken = parse_synctoken(&sync_collection.sync_token).unwrap_or(0); let (new_objects, deleted_objects, new_synctoken) = cal_store .sync_changes(principal, cal_id, old_synctoken) .await?; let mut responses = Vec::new(); for object in new_objects { let path = format!("{}/{}", req.path().trim_end_matches('/'), object.get_id()); responses.push( CalendarObjectResource { object, principal: principal.to_owned(), } .propfind(&path, &props, user, req.resource_map())?, ); } for object_id in deleted_objects { let path = format!("{}/{}", req.path().trim_end_matches('/'), object_id); responses.push(ResponseElement { href: path, status: Some(StatusCode::NOT_FOUND), ..Default::default() }); } Ok(MultistatusElement { responses, sync_token: Some(format_synctoken(new_synctoken)), ..Default::default() }) }