aboutsummaryrefslogtreecommitdiff
path: root/aero-proto/src/dav/resource.rs
diff options
context:
space:
mode:
Diffstat (limited to 'aero-proto/src/dav/resource.rs')
-rw-r--r--aero-proto/src/dav/resource.rs340
1 files changed, 340 insertions, 0 deletions
diff --git a/aero-proto/src/dav/resource.rs b/aero-proto/src/dav/resource.rs
new file mode 100644
index 0000000..9ad662a
--- /dev/null
+++ b/aero-proto/src/dav/resource.rs
@@ -0,0 +1,340 @@
+use std::sync::Arc;
+type ArcUser = std::sync::Arc<User>;
+
+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<Box<dyn DavNode>>> {
+ if path.len() == 0 {
+ let this = self.clone();
+ return async { Ok(Box::new(this) as Box<dyn DavNode>) }.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<Box<dyn DavNode>>> {
+ async { vec![Box::new(HomeNode { }) as Box<dyn DavNode>] }.boxed()
+ }
+
+ fn path(&self, user: &ArcUser) -> String {
+ "/".into()
+ }
+
+ fn supported_properties(&self, user: &ArcUser) -> dav::PropName<All> {
+ 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<All>) -> Vec<dav::AnyProperty<All>> {
+ 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<Box<dyn DavNode>>> {
+ if path.len() == 0 {
+ let node = Box::new(self.clone()) as Box<dyn DavNode>;
+ 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<Box<dyn DavNode>>> {
+ async {
+ CalendarListNode::new(user).await
+ .map(|c| vec![Box::new(c) as Box<dyn DavNode>])
+ .unwrap_or(vec![])
+ }.boxed()
+ }
+
+ fn path(&self, user: &ArcUser) -> String {
+ format!("/{}/", user.username)
+ }
+
+ fn supported_properties(&self, user: &ArcUser) -> dav::PropName<All> {
+ 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<All>) -> Vec<dav::AnyProperty<All>> {
+ 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<String>,
+}
+impl CalendarListNode {
+ async fn new(user: &ArcUser) -> Result<Self> {
+ 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<Box<dyn DavNode>>> {
+ if path.len() == 0 {
+ let node = Box::new(self.clone()) as Box<dyn DavNode>;
+ 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<Box<dyn DavNode>>> {
+ 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<dyn DavNode>)
+ .collect::<Vec<Box<dyn DavNode>>>()
+ .await
+ }.boxed()
+ }
+
+ fn path(&self, user: &ArcUser) -> String {
+ format!("/{}/calendar/", user.username)
+ }
+
+ fn supported_properties(&self, user: &ArcUser) -> dav::PropName<All> {
+ dav::PropName(vec![
+ dav::PropertyRequest::DisplayName,
+ dav::PropertyRequest::ResourceType,
+ dav::PropertyRequest::GetContentType,
+ ])
+ }
+ fn properties(&self, user: &ArcUser, prop: dav::PropName<All>) -> Vec<dav::AnyProperty<All>> {
+ 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<Calendar>,
+ calname: String,
+}
+impl DavNode for CalendarNode {
+ fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str]) -> BoxFuture<'a, Result<Box<dyn DavNode>>> {
+ if path.len() == 0 {
+ let node = Box::new(self.clone()) as Box<dyn DavNode>;
+ 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<Box<dyn DavNode>>> {
+ 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<dyn DavNode>
+ }).collect()
+ }.boxed()
+ }
+
+ fn path(&self, user: &ArcUser) -> String {
+ format!("/{}/calendar/{}/", user.username, self.calname)
+ }
+
+ fn supported_properties(&self, user: &ArcUser) -> dav::PropName<All> {
+ 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<All>) -> Vec<dav::AnyProperty<All>> {
+ 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<Calendar>,
+ calname: String,
+ filename: String,
+ blob_id: BlobId,
+}
+impl DavNode for EventNode {
+ fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str]) -> BoxFuture<'a, Result<Box<dyn DavNode>>> {
+ if path.len() == 0 {
+ let node = Box::new(self.clone()) as Box<dyn DavNode>;
+ return async { Ok(node) }.boxed()
+ }
+
+ async { Err(anyhow!("Not found")) }.boxed()
+ }
+
+ fn children<'a>(&self, user: &'a ArcUser) -> BoxFuture<'a, Vec<Box<dyn DavNode>>> {
+ 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<All> {
+ 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<All>) -> Vec<dav::AnyProperty<All>> {
+ 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()
+ }
+}