use crate::data::{BodyChunkIpld, Header, Link, MemoIpld};
use anyhow::{anyhow, Result};
use cid::Cid;
use libipld_cbor::DagCborCodec;
use noosphere_storage::{BlockStore, Storage};
use tokio::io::AsyncReadExt;
use crate::context::{internal::SphereContextInternal, HasMutableSphereContext, HasSphereContext};
use async_trait::async_trait;
use crate::context::{AsyncFileBody, SphereContentRead};
fn validate_slug(slug: &str) -> Result<()> {
if slug.is_empty() {
Err(anyhow!("Slug must not be empty."))
} else {
Ok(())
}
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
pub trait SphereContentWrite<S>: SphereContentRead<S>
where
S: Storage + 'static,
{
async fn link_raw(&mut self, slug: &str, cid: &Link<MemoIpld>) -> Result<()>;
async fn link(
&mut self,
slug: &str,
content_type: &str,
body_cid: &Cid,
additional_headers: Option<Vec<(String, String)>>,
) -> Result<Link<MemoIpld>>;
async fn write<F: AsyncFileBody>(
&mut self,
slug: &str,
content_type: &str,
mut value: F,
additional_headers: Option<Vec<(String, String)>>,
) -> Result<Link<MemoIpld>>;
async fn remove(&mut self, slug: &str) -> Result<Option<Link<MemoIpld>>>;
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl<C, S> SphereContentWrite<S> for C
where
C: HasSphereContext<S> + HasMutableSphereContext<S>,
S: Storage + 'static,
{
async fn link_raw(&mut self, slug: &str, cid: &Link<MemoIpld>) -> Result<()> {
self.assert_write_access().await?;
validate_slug(slug)?;
self.sphere_context_mut()
.await?
.mutation_mut()
.content_mut()
.set(&slug.into(), cid);
Ok(())
}
async fn link(
&mut self,
slug: &str,
content_type: &str,
body_cid: &Cid,
additional_headers: Option<Vec<(String, String)>>,
) -> Result<Link<MemoIpld>> {
self.assert_write_access().await?;
validate_slug(slug)?;
let memo_cid = {
let current_file = self.read(slug).await?;
let previous_memo_cid = current_file.map(|file| file.memo_version);
let mut sphere_context = self.sphere_context_mut().await?;
let mut new_memo = match previous_memo_cid {
Some(cid) => {
let mut memo = MemoIpld::branch_from(&cid, sphere_context.db()).await?;
memo.body = *body_cid;
memo
}
None => MemoIpld {
parent: None,
headers: Vec::new(),
body: *body_cid,
},
};
if let Some(headers) = additional_headers {
new_memo.replace_headers(headers)
}
new_memo.replace_first_header(&Header::ContentType, content_type);
sphere_context
.db_mut()
.save::<DagCborCodec, MemoIpld>(new_memo)
.await?
.into()
};
self.link_raw(slug, &memo_cid).await?;
Ok(memo_cid)
}
async fn write<F: AsyncFileBody>(
&mut self,
slug: &str,
content_type: &str,
mut value: F,
additional_headers: Option<Vec<(String, String)>>,
) -> Result<Link<MemoIpld>> {
debug!("Writing {}...", slug);
self.assert_write_access().await?;
validate_slug(slug)?;
let mut bytes = Vec::new();
value.read_to_end(&mut bytes).await?;
let body_cid =
BodyChunkIpld::store_bytes(&bytes, self.sphere_context_mut().await?.db_mut()).await?;
let header = (
Header::ContentLength.to_string(),
format!("{}", bytes.len()),
);
let additional_headers = if let Some(mut headers) = additional_headers {
headers.push(header);
headers
} else {
vec![header]
};
self.link(slug, content_type, &body_cid, Some(additional_headers))
.await
}
async fn remove(&mut self, slug: &str) -> Result<Option<Link<MemoIpld>>> {
self.assert_write_access().await?;
let current_file = self.read(slug).await?;
Ok(match current_file {
Some(file) => {
self.sphere_context_mut()
.await?
.mutation_mut()
.content_mut()
.remove(&String::from(slug));
Some(file.memo_version)
}
None => None,
})
}
}