Working ICS GET/PUT/DELETE
This commit is contained in:
parent
52d767edae
commit
e1d7cf88af
5 changed files with 105 additions and 17 deletions
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue