Working ICS GET/PUT/DELETE

This commit is contained in:
Quentin 2024-04-24 17:35:00 +02:00
parent 52d767edae
commit e1d7cf88af
Signed by: quentin
GPG key ID: E9602264D639FF68
5 changed files with 105 additions and 17 deletions

View file

@ -155,7 +155,7 @@ impl CalendarInternal {
async fn delete(&mut self, blob_id: BlobId) -> Result<Token> {
let davstate = self.davdag.state();
if davstate.table.contains_key(&blob_id) {
if !davstate.table.contains_key(&blob_id) {
bail!("Cannot delete event that doesn't exist");
}

View file

@ -202,6 +202,16 @@ impl DavDag {
}
}
impl std::fmt::Debug for DavDag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("DavDag\n")?;
for elem in self.table.iter() {
f.write_fmt(format_args!("\t{:?} => {:?}", elem.0, elem.1))?;
}
Ok(())
}
}
impl BayouState for DavDag {
type Op = DavDagOp;

View file

@ -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<HttpResponse> {
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<Box<dyn DavNode>>, not_found: Vec<dav::Href>, props: Option<dav::PropName<All>>) -> dav::Multistatus<All> {

View file

@ -39,8 +39,12 @@ pub(crate) trait DavNode: Send {
fn put<'a>(&'a self, policy: PutPolicy, stream: Content<'a>) -> BoxFuture<'a, std::result::Result<Etag, std::io::Error>>;
/// Content type of the element
fn content_type(&self) -> &str;
/// Get ETag
fn etag(&self) -> BoxFuture<Option<Etag>>;
/// Get content
fn content(&self) -> Content<'static>;
/// Delete
fn delete(&self) -> BoxFuture<std::result::Result<(), std::io::Error>>;
//@FIXME maybe add etag, maybe add a way to set content

View file

@ -78,6 +78,14 @@ impl DavNode for RootNode {
fn content_type(&self) -> &str {
"text/plain"
}
fn etag(&self) -> BoxFuture<Option<Etag>> {
async { None }.boxed()
}
fn delete(&self) -> BoxFuture<std::result::Result<(), std::io::Error>> {
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<Option<Etag>> {
async { None }.boxed()
}
fn delete(&self) -> BoxFuture<std::result::Result<(), std::io::Error>> {
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<Option<Etag>> {
async { None }.boxed()
}
fn delete(&self) -> BoxFuture<std::result::Result<(), std::io::Error>> {
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<Etag, std::io::Error>> {
fn put<'a>(&'a self, _policy: PutPolicy, _stream: Content<'a>) -> BoxFuture<'a, std::result::Result<Etag, std::io::Error>> {
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<Option<Etag>> {
async { None }.boxed()
}
fn delete(&self) -> BoxFuture<std::result::Result<(), std::io::Error>> {
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<Etag> {
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<Box<dyn DavNode>>> {
@ -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<Etag, std::io::Error>> {
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<Option<Etag>> {
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<std::result::Result<(), std::io::Error>> {
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<Option<Etag>> {
async { None }.boxed()
}
fn delete(&self) -> BoxFuture<std::result::Result<(), std::io::Error>> {
// Nothing to delete
async { Ok(()) }.boxed()
}
}