415 lines
16 KiB
Rust
415 lines
16 KiB
Rust
use std::sync::Arc;
|
|
type ArcUser = std::sync::Arc<User>;
|
|
|
|
use anyhow::{anyhow, Result};
|
|
use futures::stream::{TryStream, 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, PutPolicy, Content};
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) struct RootNode {}
|
|
impl DavNode for RootNode {
|
|
fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str], create: bool) -> 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..], create);
|
|
}
|
|
|
|
//@NOTE: We can't create a node at this level
|
|
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()
|
|
}
|
|
|
|
fn put(&self, policy: PutPolicy, stream: Content) -> BoxFuture<Result<()>> {
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) struct HomeNode {}
|
|
impl DavNode for HomeNode {
|
|
fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str], create: bool) -> 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 move {
|
|
let child = Box::new(CalendarListNode::new(user).await?);
|
|
child.fetch(user, &path[1..], create).await
|
|
}.boxed();
|
|
}
|
|
|
|
//@NOTE: we can't create a node at this level
|
|
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(
|
|
//@FIXME we are hardcoding the calendar path, instead we would want to use
|
|
//objects
|
|
format!("/{}/calendar/", user.username)
|
|
))))),
|
|
v => dav::AnyProperty::Request(v),
|
|
}).collect()
|
|
}
|
|
|
|
fn put(&self, policy: PutPolicy, stream: Content) -> BoxFuture<Result<()>> {
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
#[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], create: bool) -> 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 move {
|
|
//@FIXME: we should create a node if the open returns a "not found".
|
|
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..], create).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()
|
|
}
|
|
|
|
fn put(&self, policy: PutPolicy, stream: Content) -> BoxFuture<Result<()>> {
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
#[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], create: bool) -> 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 {
|
|
match (col.dag().await.idx_by_filename.get(path[0]), create) {
|
|
(Some(blob_id), _) => {
|
|
let child = Box::new(EventNode {
|
|
col: col.clone(),
|
|
calname,
|
|
filename: path[0].to_string(),
|
|
blob_id: *blob_id,
|
|
});
|
|
child.fetch(user, &path[1..], create).await
|
|
},
|
|
(None, true) => {
|
|
let child = Box::new(CreateEventNode {
|
|
col: col.clone(),
|
|
calname,
|
|
filename: path[0].to_string(),
|
|
});
|
|
child.fetch(user, &path[1..], create).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()
|
|
}
|
|
|
|
fn put(&self, policy: PutPolicy, stream: Content) -> BoxFuture<Result<()>> {
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
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], create: bool) -> 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 supported: can't create a child on an event node")) }.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()
|
|
}
|
|
|
|
fn put(&self, policy: PutPolicy, stream: Content) -> BoxFuture<Result<()>> {
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) struct CreateEventNode {
|
|
col: Arc<Calendar>,
|
|
calname: String,
|
|
filename: String,
|
|
}
|
|
impl DavNode for CreateEventNode {
|
|
fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str], create: bool) -> 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 supported: can't create a child on an event node")) }.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![])
|
|
}
|
|
|
|
fn properties(&self, _user: &ArcUser, prop: dav::PropName<All>) -> Vec<dav::AnyProperty<All>> {
|
|
vec![]
|
|
}
|
|
|
|
fn put(&self, policy: PutPolicy, stream: Content) -> BoxFuture<Result<()>> {
|
|
//@TODO write file
|
|
todo!()
|
|
}
|
|
}
|