master
Astro 2020-12-11 01:06:20 +01:00
parent c0da68f48c
commit 57758cdc9a
3 changed files with 42 additions and 7 deletions

26
README.md Normal file
View File

@ -0,0 +1,26 @@
# Simple Flash Key Value store
Tries to keep flash wear low by continuously appending data,
automatically erasing and rewriting when full.
## Store requirements
* Reading must be possible by memory-mapping a `&[u8]` slice
* Must support `erase()` for compaction,
* Writing must support `program()` with an offset and `&[u8]` payload
For details see `trait StoreBackend`.
## TODO
* NoFlash backend
* read_int()
* write_str()
* automatic value coercion
* support for floats
### Ideas
* iterator (quadratic)
* compaction to a second backend instead on stack
* StoreBackend-configurable erased data detection

View File

@ -18,10 +18,14 @@ impl<B> From<WriteError<B>> for Error<B> {
}
}
/// Errors decoding the store
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReadError {
/// invalid record size
InvalidSize { offset: usize, size: usize },
/// missing separator
MissingSeparator { offset: usize },
/// `str::from_utf8` error
Utf8Error(str::Utf8Error),
}
@ -38,9 +42,13 @@ impl fmt::Display for ReadError {
}
}
/// Errors when writing to the store
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WriteError<B> {
/// store is full even after automatic compaction
SpaceExhausted,
/// error passed from the StoreBackend's `erase()` and `program()`
/// methods
Backend(B),
}

View File

@ -20,26 +20,25 @@ pub trait StoreBackend {
size_of::<Self::Data>()
}
/// utility for compaction, requires compile-time knowledge of the data length
type Error;
fn erase(&mut self) -> Result<(), Self::Error>;
fn program(&mut self, offset: usize, payload: &[u8]) -> Result<(), Self::Error>;
/// called after many `program()` invocations to allow for eg. cache flushing
/// called after repeated `program()` invocations to allow for eg. cache flushing
fn program_done(&mut self) {}
}
// TODO: NoFlash
/// Simple Flash Key Value Store
pub struct Store<B: StoreBackend> {
backend: B,
}
impl<B: StoreBackend> Store<B> {
/// wrap a `StoreBackend` into a store
pub fn new(backend: B) -> Self {
Store { backend }
}
/// read from `key`
pub fn read(&self, key: &str) -> Result<Option<&[u8]>, ReadError> {
let mut iter = Iter::new(self.backend.data().as_ref());
let mut value = None;
@ -53,6 +52,7 @@ impl<B: StoreBackend> Store<B> {
Ok(value)
}
/// read from `key`, decode UTF-8
pub fn read_str(&self, key: &str) -> Result<Option<&str>, ReadError> {
self.read(key)
.and_then(|value| value
@ -134,6 +134,7 @@ impl<B: StoreBackend> Store<B> {
Ok(())
}
/// store a buffer `value` at `key`
pub fn write(&mut self, key: &str, value: &[u8]) -> Result<(), Error<B::Error>> {
match self.append(key, value) {
Err(Error::Write(WriteError::SpaceExhausted)) => {
@ -144,6 +145,7 @@ impl<B: StoreBackend> Store<B> {
}
}
/// serialize 32-bit as ASCII, store at `key`
pub fn write_int(&mut self, key: &str, value: u32) -> Result<(), Error<B::Error>> {
let mut buf = [0; 16];
let mut wrapper = FmtWrapper::new(&mut buf);
@ -151,6 +153,7 @@ impl<B: StoreBackend> Store<B> {
self.write(key, wrapper.contents())
}
/// store a 0-byte tombstone value at `key`
pub fn remove(&mut self, key: &str) -> Result<(), Error<B::Error>> {
self.write(key, &[])
}
@ -162,5 +165,3 @@ impl<B: StoreBackend> Store<B> {
Ok(())
}
}
// TODO: quadratic iterator