use ekv::{Database, WriteTransaction, flash::Flash};
use embassy_sync::blocking_mutex::raw::RawMutex;
use heapless::String;
use crate::error::Error;
use crate::file::EkvFile;
use crate::key::{self, KEY_BUF_CAP, KEY_SUFFIX_OVERHEAD};
use crate::meta::{FileMeta, META_BUF_LEN};
pub struct EkvFs<'a, F: Flash, M: RawMutex, const MAX_PATH_LEN: usize, const CHUNK_SIZE: usize> {
db: &'a Database<F, M>,
}
impl<'a, F: Flash, M: RawMutex, const MAX_PATH_LEN: usize, const CHUNK_SIZE: usize>
EkvFs<'a, F, M, MAX_PATH_LEN, CHUNK_SIZE>
{
const _KEY_LEN_OK: () = assert!(MAX_PATH_LEN + KEY_SUFFIX_OVERHEAD <= KEY_BUF_CAP);
pub const fn new(db: &'a Database<F, M>) -> Self {
Self { db }
}
pub async fn stat(&self, path: &str) -> Result<FileMeta, Error> {
key::check_path(path, MAX_PATH_LEN)?;
self.read_meta(path).await
}
pub async fn open(
&self,
path: &str,
) -> Result<EkvFile<'a, F, M, MAX_PATH_LEN, CHUNK_SIZE>, Error> {
key::check_path(path, MAX_PATH_LEN)?;
let meta = self.read_meta(path).await?;
let mut safe_path = String::<MAX_PATH_LEN>::new();
safe_path.push_str(path).map_err(|_| Error::PathTooLong)?;
Ok(EkvFile::new(self.db, safe_path, meta))
}
pub async fn write_file(&self, path: &str, data: &[u8]) -> Result<(), Error> {
key::check_path(path, MAX_PATH_LEN)?;
if self.read_meta(path).await.is_ok() {
self.delete_file(path).await?;
}
let mut tx = self.db.write_transaction().await;
let chunk_count = data.chunks(CHUNK_SIZE).count();
let mut key_buf = String::<KEY_BUF_CAP>::new();
for (i, chunk_data) in data.chunks(CHUNK_SIZE).enumerate() {
key::format_chunk_key(&mut key_buf, path, i)?;
tx.write(key_buf.as_bytes(), chunk_data)
.await
.map_err(Error::from_db)?;
}
let meta = FileMeta {
size: data.len(),
chunks: chunk_count,
};
key::format_meta_key(&mut key_buf, path)?;
let mut meta_buf = [0u8; META_BUF_LEN];
let meta_bytes = postcard::to_slice(&meta, &mut meta_buf).map_err(|_| Error::Serialize)?;
tx.write(key_buf.as_bytes(), meta_bytes)
.await
.map_err(Error::from_db)?;
tx.commit().await.map_err(Error::from_db)?;
Ok(())
}
pub async fn delete_file(&self, path: &str) -> Result<(), Error> {
key::check_path(path, MAX_PATH_LEN)?;
let meta = self.read_meta(path).await?;
let mut tx = self.db.write_transaction().await;
Self::remove_chunks(&mut tx, path, meta.chunks).await?;
Self::remove_meta(&mut tx, path).await?;
tx.commit().await.map_err(Error::from_db)?;
Ok(())
}
async fn read_meta(&self, path: &str) -> Result<FileMeta, Error> {
let mut key_buf = String::<KEY_BUF_CAP>::new();
key::format_meta_key(&mut key_buf, path)?;
let mut buf = [0u8; META_BUF_LEN];
let tx = self.db.read_transaction().await;
match tx.read(key_buf.as_bytes(), &mut buf).await {
Ok(len) => postcard::from_bytes(&buf[..len]).map_err(|_| Error::Serialize),
Err(_) => Err(Error::NotFound),
}
}
async fn remove_chunks(
tx: &mut WriteTransaction<'_, F, M>,
path: &str,
chunk_count: usize,
) -> Result<(), Error> {
let mut key_buf = String::<KEY_BUF_CAP>::new();
for i in 0..chunk_count {
key::format_chunk_key(&mut key_buf, path, i)?;
tx.delete(key_buf.as_bytes())
.await
.map_err(Error::from_db)?;
}
Ok(())
}
async fn remove_meta(tx: &mut WriteTransaction<'_, F, M>, path: &str) -> Result<(), Error> {
let mut key_buf = String::<KEY_BUF_CAP>::new();
key::format_meta_key(&mut key_buf, path)?;
tx.delete(key_buf.as_bytes())
.await
.map_err(Error::from_db)?;
Ok(())
}
}