From e1d7cf88afd9baab67d53823e95cb1b7f240802f Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Wed, 24 Apr 2024 17:35:00 +0200 Subject: Working ICS GET/PUT/DELETE --- aero-proto/src/dav/controller.rs | 23 +++++++---- aero-proto/src/dav/node.rs | 4 ++ aero-proto/src/dav/resource.rs | 83 +++++++++++++++++++++++++++++++++++----- 3 files changed, 94 insertions(+), 16 deletions(-) (limited to 'aero-proto/src') diff --git a/aero-proto/src/dav/controller.rs b/aero-proto/src/dav/controller.rs index aee86fa..f3b5496 100644 --- a/aero-proto/src/dav/controller.rs +++ b/aero-proto/src/dav/controller.rs @@ -71,9 +71,7 @@ impl Controller { }, "GET" => ctrl.get().await, "PUT" => ctrl.put().await, - "DELETE" => { - todo!(); - }, + "DELETE" => ctrl.delete().await, "PROPFIND" => ctrl.propfind().await, "REPORT" => ctrl.report().await, _ => Ok(Response::builder() @@ -206,14 +204,25 @@ impl Controller { let stream_body = StreamBody::new(self.node.content().map_ok(|v| Frame::data(v))); let boxed_body = UnsyncBoxBody::new(stream_body); - let response = Response::builder() - .status(200) - .header("content-type", self.node.content_type()) - .body(boxed_body)?; + let mut builder = Response::builder().status(200); + builder = builder.header("content-type", self.node.content_type()); + if let Some(etag) = self.node.etag().await { + builder = builder.header("etag", etag); + } + let response = builder.body(boxed_body)?; Ok(response) } + async fn delete(self) -> Result { + self.node.delete().await?; + let response = Response::builder() + .status(204) + //.header("content-type", "application/xml; charset=\"utf-8\"") + .body(text_body(""))?; + Ok(response) + } + // --- Common utility functions --- /// Build a multistatus response from a list of DavNodes async fn multistatus(user: &ArcUser, nodes: Vec>, not_found: Vec, props: Option>) -> dav::Multistatus { diff --git a/aero-proto/src/dav/node.rs b/aero-proto/src/dav/node.rs index 1ed4b0a..d246280 100644 --- a/aero-proto/src/dav/node.rs +++ b/aero-proto/src/dav/node.rs @@ -39,8 +39,12 @@ pub(crate) trait DavNode: Send { fn put<'a>(&'a self, policy: PutPolicy, stream: Content<'a>) -> BoxFuture<'a, std::result::Result>; /// Content type of the element fn content_type(&self) -> &str; + /// Get ETag + fn etag(&self) -> BoxFuture>; /// Get content fn content(&self) -> Content<'static>; + /// Delete + fn delete(&self) -> BoxFuture>; //@FIXME maybe add etag, maybe add a way to set content diff --git a/aero-proto/src/dav/resource.rs b/aero-proto/src/dav/resource.rs index 9e2ce3d..944c6c8 100644 --- a/aero-proto/src/dav/resource.rs +++ b/aero-proto/src/dav/resource.rs @@ -78,6 +78,14 @@ impl DavNode for RootNode { fn content_type(&self) -> &str { "text/plain" } + + fn etag(&self) -> BoxFuture> { + async { None }.boxed() + } + + fn delete(&self) -> BoxFuture> { + async { Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied)) }.boxed() + } } #[derive(Clone)] @@ -151,10 +159,17 @@ impl DavNode for HomeNode { futures::stream::once(futures::future::err(std::io::Error::from(std::io::ErrorKind::Unsupported))).boxed() } - fn content_type(&self) -> &str { "text/plain" } + + fn etag(&self) -> BoxFuture> { + async { None }.boxed() + } + + fn delete(&self) -> BoxFuture> { + async { Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied)) }.boxed() + } } #[derive(Clone)] @@ -241,6 +256,14 @@ impl DavNode for CalendarListNode { fn content_type(&self) -> &str { "text/plain" } + + fn etag(&self) -> BoxFuture> { + async { None }.boxed() + } + + fn delete(&self) -> BoxFuture> { + async { Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied)) }.boxed() + } } #[derive(Clone)] @@ -333,7 +356,7 @@ impl DavNode for CalendarNode { }).boxed() } - fn put<'a>(&'a self, _policy: PutPolicy, stream: Content<'a>) -> BoxFuture<'a, std::result::Result> { + fn put<'a>(&'a self, _policy: PutPolicy, _stream: Content<'a>) -> BoxFuture<'a, std::result::Result> { futures::future::err(std::io::Error::from(std::io::ErrorKind::Unsupported)).boxed() } @@ -344,6 +367,14 @@ impl DavNode for CalendarNode { fn content_type(&self) -> &str { "text/plain" } + + fn etag(&self) -> BoxFuture> { + async { None }.boxed() + } + + fn delete(&self) -> BoxFuture> { + async { Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied)) }.boxed() + } } #[derive(Clone)] @@ -353,11 +384,6 @@ pub(crate) struct EventNode { filename: String, blob_id: BlobId, } -impl EventNode { - async fn etag(&self) -> Result { - self.col.dag().await.table.get(&self.blob_id).map(|(_, _, etag)| etag.to_string()).ok_or(anyhow!("Missing blob id in index")) - } -} impl DavNode for EventNode { fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str], create: bool) -> BoxFuture<'a, Result>> { @@ -396,7 +422,10 @@ impl DavNode for EventNode { dav::PropertyRequest::DisplayName => dav::Property::DisplayName(format!("{} event", this.filename)), dav::PropertyRequest::ResourceType => dav::Property::ResourceType(vec![]), dav::PropertyRequest::GetContentType => dav::Property::GetContentType("text/calendar".into()), - dav::PropertyRequest::GetEtag => dav::Property::GetEtag("\"abcdefg\"".into()), + dav::PropertyRequest::GetEtag => { + let etag = this.etag().await.ok_or(n.clone())?; + dav::Property::GetEtag(etag) + }, dav::PropertyRequest::Extension(all::PropertyRequest::Cal(cal::PropertyRequest::CalendarData(_req))) => { let ics = String::from_utf8(this.col.get(this.blob_id).await.or(Err(n.clone()))?).or(Err(n.clone()))?; @@ -414,7 +443,7 @@ impl DavNode for EventNode { fn put<'a>(&'a self, policy: PutPolicy, stream: Content<'a>) -> BoxFuture<'a, std::result::Result> { async { - let existing_etag = self.etag().await.or(Err(std::io::Error::new(std::io::ErrorKind::Other, "Etag error")))?; + let existing_etag = self.etag().await.ok_or(std::io::Error::new(std::io::ErrorKind::Other, "Etag error"))?; match policy { PutPolicy::CreateOnly => return Err(std::io::Error::from(std::io::ErrorKind::AlreadyExists)), PutPolicy::ReplaceEtag(etag) if etag != existing_etag.as_str() => return Err(std::io::Error::from(std::io::ErrorKind::AlreadyExists)), @@ -427,6 +456,7 @@ impl DavNode for EventNode { let mut reader = stream.into_async_read(); reader.read_to_end(&mut evt).await.or(Err(std::io::Error::from(std::io::ErrorKind::BrokenPipe)))?; let (_token, entry) = self.col.put(self.filename.as_str(), evt.as_ref()).await.or(Err(std::io::ErrorKind::Interrupted))?; + self.col.opportunistic_sync().await.or(Err(std::io::ErrorKind::ConnectionReset))?; Ok(entry.2) }.boxed() } @@ -446,6 +476,31 @@ impl DavNode for EventNode { fn content_type(&self) -> &str { "text/calendar" } + + fn etag(&self) -> BoxFuture> { + let calendar = self.col.clone(); + + async move { + calendar.dag().await.table.get(&self.blob_id).map(|(_, _, etag)| etag.to_string()) + }.boxed() + } + + fn delete(&self) -> BoxFuture> { + let calendar = self.col.clone(); + let blob_id = self.blob_id.clone(); + + async move { + let _token = match calendar.delete(blob_id).await { + Ok(v) => v, + Err(e) => { + tracing::error!(err=?e, "delete event node"); + return Err(std::io::Error::from(std::io::ErrorKind::Interrupted)) + }, + }; + calendar.opportunistic_sync().await.or(Err(std::io::ErrorKind::ConnectionReset))?; + Ok(()) + }.boxed() + } } #[derive(Clone)] @@ -489,6 +544,7 @@ impl DavNode for CreateEventNode { let mut reader = stream.into_async_read(); reader.read_to_end(&mut evt).await.unwrap(); let (_token, entry) = self.col.put(self.filename.as_str(), evt.as_ref()).await.or(Err(std::io::ErrorKind::Interrupted))?; + self.col.opportunistic_sync().await.or(Err(std::io::ErrorKind::ConnectionReset))?; Ok(entry.2) }.boxed() } @@ -500,4 +556,13 @@ impl DavNode for CreateEventNode { fn content_type(&self) -> &str { "text/plain" } + + fn etag(&self) -> BoxFuture> { + async { None }.boxed() + } + + fn delete(&self) -> BoxFuture> { + // Nothing to delete + async { Ok(()) }.boxed() + } } -- cgit v1.2.3