use std::sync::Arc; type ArcUser = std::sync::Arc; use anyhow::{anyhow, Result}; use futures::stream::StreamExt; use futures::{future::BoxFuture, future::FutureExt}; use aero_collections::{user::User, calendar::Calendar, davdag::BlobId}; use aero_dav::types as dav; use aero_dav::caltypes as cal; use aero_dav::acltypes as acl; use aero_dav::realization::{All, self as all}; use crate::dav::node::DavNode; #[derive(Clone)] pub(crate) struct RootNode {} impl DavNode for RootNode { fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str]) -> BoxFuture<'a, Result>> { if path.len() == 0 { let this = self.clone(); return async { Ok(Box::new(this) as Box) }.boxed(); } if path[0] == user.username { let child = Box::new(HomeNode {}); return child.fetch(user, &path[1..]); } async { Err(anyhow!("Not found")) }.boxed() } fn children<'a>(&self, user: &'a ArcUser) -> BoxFuture<'a, Vec>> { async { vec![Box::new(HomeNode { }) as Box] }.boxed() } fn path(&self, user: &ArcUser) -> String { "/".into() } fn supported_properties(&self, user: &ArcUser) -> dav::PropName { dav::PropName(vec![ dav::PropertyRequest::DisplayName, dav::PropertyRequest::ResourceType, dav::PropertyRequest::GetContentType, dav::PropertyRequest::Extension(all::PropertyRequest::Acl(acl::PropertyRequest::CurrentUserPrincipal)), ]) } fn properties(&self, user: &ArcUser, prop: dav::PropName) -> Vec> { prop.0.into_iter().map(|n| match n { dav::PropertyRequest::DisplayName => dav::AnyProperty::Value(dav::Property::DisplayName("DAV Root".to_string())), dav::PropertyRequest::ResourceType => dav::AnyProperty::Value(dav::Property::ResourceType(vec![ dav::ResourceType::Collection, ])), dav::PropertyRequest::GetContentType => dav::AnyProperty::Value(dav::Property::GetContentType("httpd/unix-directory".into())), dav::PropertyRequest::Extension(all::PropertyRequest::Acl(acl::PropertyRequest::CurrentUserPrincipal)) => dav::AnyProperty::Value(dav::Property::Extension(all::Property::Acl(acl::Property::CurrentUserPrincipal(acl::User::Authenticated(dav::Href(HomeNode{}.path(user))))))), v => dav::AnyProperty::Request(v), }).collect() } } #[derive(Clone)] pub(crate) struct HomeNode {} impl DavNode for HomeNode { fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str]) -> BoxFuture<'a, Result>> { if path.len() == 0 { let node = Box::new(self.clone()) as Box; return async { Ok(node) }.boxed() } if path[0] == "calendar" { return async { let child = Box::new(CalendarListNode::new(user).await?); child.fetch(user, &path[1..]).await }.boxed(); } async { Err(anyhow!("Not found")) }.boxed() } fn children<'a>(&self, user: &'a ArcUser) -> BoxFuture<'a, Vec>> { async { CalendarListNode::new(user).await .map(|c| vec![Box::new(c) as Box]) .unwrap_or(vec![]) }.boxed() } fn path(&self, user: &ArcUser) -> String { format!("/{}/", user.username) } fn supported_properties(&self, user: &ArcUser) -> dav::PropName { dav::PropName(vec![ dav::PropertyRequest::DisplayName, dav::PropertyRequest::ResourceType, dav::PropertyRequest::GetContentType, dav::PropertyRequest::Extension(all::PropertyRequest::Cal(cal::PropertyRequest::CalendarHomeSet)), ]) } fn properties(&self, user: &ArcUser, prop: dav::PropName) -> Vec> { prop.0.into_iter().map(|n| match n { dav::PropertyRequest::DisplayName => dav::AnyProperty::Value(dav::Property::DisplayName(format!("{} home", user.username))), dav::PropertyRequest::ResourceType => dav::AnyProperty::Value(dav::Property::ResourceType(vec![ dav::ResourceType::Collection, dav::ResourceType::Extension(all::ResourceType::Acl(acl::ResourceType::Principal)), ])), dav::PropertyRequest::GetContentType => dav::AnyProperty::Value(dav::Property::GetContentType("httpd/unix-directory".into())), dav::PropertyRequest::Extension(all::PropertyRequest::Cal(cal::PropertyRequest::CalendarHomeSet)) => dav::AnyProperty::Value(dav::Property::Extension(all::Property::Cal(cal::Property::CalendarHomeSet(dav::Href(/*CalendarListNode{}.path(user)*/ todo!()))))), v => dav::AnyProperty::Request(v), }).collect() } } #[derive(Clone)] pub(crate) struct CalendarListNode { list: Vec, } impl CalendarListNode { async fn new(user: &ArcUser) -> Result { let list = user.calendars.list(user).await?; Ok(Self { list }) } } impl DavNode for CalendarListNode { fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str]) -> BoxFuture<'a, Result>> { if path.len() == 0 { let node = Box::new(self.clone()) as Box; return async { Ok(node) }.boxed(); } async { let cal = user.calendars.open(user, path[0]).await?.ok_or(anyhow!("Not found"))?; let child = Box::new(CalendarNode { col: cal, calname: path[0].to_string() }); child.fetch(user, &path[1..]).await }.boxed() } fn children<'a>(&self, user: &'a ArcUser) -> BoxFuture<'a, Vec>> { let list = self.list.clone(); async move { //@FIXME maybe we want to be lazy here?! futures::stream::iter(list.iter()) .filter_map(|name| async move { user.calendars.open(user, name).await .ok() .flatten() .map(|v| (name, v)) }) .map(|(name, cal)| Box::new(CalendarNode { col: cal, calname: name.to_string(), }) as Box) .collect::>>() .await }.boxed() } fn path(&self, user: &ArcUser) -> String { format!("/{}/calendar/", user.username) } fn supported_properties(&self, user: &ArcUser) -> dav::PropName { dav::PropName(vec![ dav::PropertyRequest::DisplayName, dav::PropertyRequest::ResourceType, dav::PropertyRequest::GetContentType, ]) } fn properties(&self, user: &ArcUser, prop: dav::PropName) -> Vec> { prop.0.into_iter().map(|n| match n { dav::PropertyRequest::DisplayName => dav::AnyProperty::Value(dav::Property::DisplayName(format!("{} calendars", user.username))), dav::PropertyRequest::ResourceType => dav::AnyProperty::Value(dav::Property::ResourceType(vec![dav::ResourceType::Collection])), dav::PropertyRequest::GetContentType => dav::AnyProperty::Value(dav::Property::GetContentType("httpd/unix-directory".into())), v => dav::AnyProperty::Request(v), }).collect() } } #[derive(Clone)] pub(crate) struct CalendarNode { col: Arc, calname: String, } impl DavNode for CalendarNode { fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str]) -> BoxFuture<'a, Result>> { if path.len() == 0 { let node = Box::new(self.clone()) as Box; return async { Ok(node) }.boxed() } let col = self.col.clone(); let calname = self.calname.clone(); async move { if let Some(blob_id) = col.dag().await.idx_by_filename.get(path[0]) { let child = Box::new(EventNode { col: col.clone(), calname, filename: path[0].to_string(), blob_id: *blob_id, }); return child.fetch(user, &path[1..]).await } Err(anyhow!("Not found")) }.boxed() } fn children<'a>(&self, user: &'a ArcUser) -> BoxFuture<'a, Vec>> { let col = self.col.clone(); let calname = self.calname.clone(); async move { col.dag().await.idx_by_filename.iter().map(|(filename, blob_id)| { Box::new(EventNode { col: col.clone(), calname: calname.clone(), filename: filename.to_string(), blob_id: *blob_id, }) as Box }).collect() }.boxed() } fn path(&self, user: &ArcUser) -> String { format!("/{}/calendar/{}/", user.username, self.calname) } fn supported_properties(&self, user: &ArcUser) -> dav::PropName { dav::PropName(vec![ dav::PropertyRequest::DisplayName, dav::PropertyRequest::ResourceType, dav::PropertyRequest::GetContentType, dav::PropertyRequest::Extension(all::PropertyRequest::Cal(cal::PropertyRequest::SupportedCalendarComponentSet)), ]) } fn properties(&self, _user: &ArcUser, prop: dav::PropName) -> Vec> { prop.0.into_iter().map(|n| match n { dav::PropertyRequest::DisplayName => dav::AnyProperty::Value(dav::Property::DisplayName(format!("{} calendar", self.calname))), dav::PropertyRequest::ResourceType => dav::AnyProperty::Value(dav::Property::ResourceType(vec![ dav::ResourceType::Collection, dav::ResourceType::Extension(all::ResourceType::Cal(cal::ResourceType::Calendar)), ])), //dav::PropertyRequest::GetContentType => dav::AnyProperty::Value(dav::Property::GetContentType("httpd/unix-directory".into())), //@FIXME seems wrong but seems to be what Thunderbird expects... dav::PropertyRequest::GetContentType => dav::AnyProperty::Value(dav::Property::GetContentType("text/calendar".into())), dav::PropertyRequest::Extension(all::PropertyRequest::Cal(cal::PropertyRequest::SupportedCalendarComponentSet)) => dav::AnyProperty::Value(dav::Property::Extension(all::Property::Cal(cal::Property::SupportedCalendarComponentSet(vec![ cal::CompSupport(cal::Component::VEvent), ])))), v => dav::AnyProperty::Request(v), }).collect() } } const FAKE_ICS: &str = r#"BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Example Corp.//CalDAV Client//EN BEGIN:VTIMEZONE LAST-MODIFIED:20040110T032845Z TZID:US/Eastern BEGIN:DAYLIGHT DTSTART:20000404T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:20001026T020000 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE BEGIN:VEVENT DTSTAMP:20240406T001102Z DTSTART;TZID=US/Eastern:20240406T100000 DURATION:PT1H SUMMARY:Event #1 Description:Go Steelers! UID:74855313FA803DA593CD579A@example.com END:VEVENT END:VCALENDAR"#; #[derive(Clone)] pub(crate) struct EventNode { col: Arc, calname: String, filename: String, blob_id: BlobId, } impl DavNode for EventNode { fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str]) -> BoxFuture<'a, Result>> { if path.len() == 0 { let node = Box::new(self.clone()) as Box; return async { Ok(node) }.boxed() } async { Err(anyhow!("Not found")) }.boxed() } fn children<'a>(&self, user: &'a ArcUser) -> BoxFuture<'a, Vec>> { async { vec![] }.boxed() } fn path(&self, user: &ArcUser) -> String { format!("/{}/calendar/{}/{}", user.username, self.calname, self.filename) } fn supported_properties(&self, user: &ArcUser) -> dav::PropName { dav::PropName(vec![ dav::PropertyRequest::DisplayName, dav::PropertyRequest::ResourceType, dav::PropertyRequest::GetEtag, dav::PropertyRequest::Extension(all::PropertyRequest::Cal(cal::PropertyRequest::CalendarData(cal::CalendarDataRequest::default()))), ]) } fn properties(&self, _user: &ArcUser, prop: dav::PropName) -> Vec> { prop.0.into_iter().map(|n| match n { dav::PropertyRequest::DisplayName => dav::AnyProperty::Value(dav::Property::DisplayName(format!("{} event", self.filename))), dav::PropertyRequest::ResourceType => dav::AnyProperty::Value(dav::Property::ResourceType(vec![])), dav::PropertyRequest::GetContentType => dav::AnyProperty::Value(dav::Property::GetContentType("text/calendar".into())), dav::PropertyRequest::GetEtag => dav::AnyProperty::Value(dav::Property::GetEtag("\"abcdefg\"".into())), dav::PropertyRequest::Extension(all::PropertyRequest::Cal(cal::PropertyRequest::CalendarData(req))) => dav::AnyProperty::Value(dav::Property::Extension(all::Property::Cal(cal::Property::CalendarData(cal::CalendarDataPayload { mime: None, payload: FAKE_ICS.into() })))), v => dav::AnyProperty::Request(v), }).collect() } }