1use ekv::{Database, flash::Flash};
4use embassy_sync::blocking_mutex::raw::RawMutex;
5use heapless::String;
6
7use crate::error::Error;
8use crate::key::{self, KEY_BUF_CAP, KEY_SUFFIX_OVERHEAD};
9use crate::meta::FileMeta;
10
11pub struct EkvFile<'a, F: Flash, M: RawMutex, const MAX_PATH_LEN: usize, const CHUNK_SIZE: usize> {
13 db: &'a Database<F, M>,
14 path: String<MAX_PATH_LEN>,
15 meta: FileMeta,
16
17 current_chunk: usize,
18 chunk_offset: usize,
19 cache: [u8; CHUNK_SIZE],
20 cache_valid: bool,
21}
22
23impl<'a, F: Flash, M: RawMutex, const MAX_PATH_LEN: usize, const CHUNK_SIZE: usize>
24 EkvFile<'a, F, M, MAX_PATH_LEN, CHUNK_SIZE>
25{
26 const _KEY_LEN_OK: () = assert!(MAX_PATH_LEN + KEY_SUFFIX_OVERHEAD <= KEY_BUF_CAP);
27
28 pub(crate) fn new(db: &'a Database<F, M>, path: String<MAX_PATH_LEN>, meta: FileMeta) -> Self {
29 Self {
30 db,
31 path,
32 meta,
33 current_chunk: 0,
34 chunk_offset: 0,
35 cache: [0; CHUNK_SIZE],
36 cache_valid: false,
37 }
38 }
39
40 pub const fn meta(&self) -> &FileMeta {
42 &self.meta
43 }
44
45 pub const fn size(&self) -> usize {
47 self.meta.size
48 }
49
50 pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
54 if self.current_chunk >= self.meta.chunks {
55 return Ok(0);
56 }
57
58 if !self.cache_valid {
59 let mut key_buf = String::<KEY_BUF_CAP>::new();
60 key::format_chunk_key(&mut key_buf, &self.path, self.current_chunk)?;
61
62 let tx = self.db.read_transaction().await;
63 tx.read(key_buf.as_bytes(), &mut self.cache)
64 .await
65 .map_err(Error::from_db)?;
66 self.cache_valid = true;
67 }
68
69 let total_read_so_far = (self.current_chunk * CHUNK_SIZE) + self.chunk_offset;
70 let bytes_left_in_file = self.meta.size.saturating_sub(total_read_so_far);
71 let bytes_left_in_chunk = CHUNK_SIZE - self.chunk_offset;
72
73 let to_copy = core::cmp::min(buf.len(), bytes_left_in_chunk);
74 let to_copy = core::cmp::min(to_copy, bytes_left_in_file);
75
76 if to_copy == 0 {
77 return Ok(0);
78 }
79
80 buf[..to_copy].copy_from_slice(&self.cache[self.chunk_offset..self.chunk_offset + to_copy]);
81 self.chunk_offset += to_copy;
82
83 if self.chunk_offset == CHUNK_SIZE || total_read_so_far + to_copy == self.meta.size {
84 self.current_chunk += 1;
85 self.chunk_offset = 0;
86 self.cache_valid = false;
87 }
88
89 Ok(to_copy)
90 }
91}