use crate::document::PeatDocument;
use crate::error::Result;
#[cfg(feature = "std")]
use std::sync::{Arc, RwLock};
pub trait DocumentStore: Send + Sync {
fn save(&mut self, doc: &PeatDocument) -> Result<()>;
fn load(&self) -> Result<Option<PeatDocument>>;
fn clear(&mut self) -> Result<()>;
fn has_document(&self) -> bool {
self.load().ok().flatten().is_some()
}
}
#[cfg(feature = "std")]
#[derive(Default)]
pub struct MemoryStore {
document: RwLock<Option<PeatDocument>>,
}
#[cfg(feature = "std")]
impl MemoryStore {
pub fn new() -> Self {
Self::default()
}
pub fn with_document(doc: PeatDocument) -> Self {
Self {
document: RwLock::new(Some(doc)),
}
}
}
#[cfg(feature = "std")]
impl DocumentStore for MemoryStore {
fn save(&mut self, doc: &PeatDocument) -> Result<()> {
let mut stored = self.document.write().unwrap();
*stored = Some(doc.clone());
Ok(())
}
fn load(&self) -> Result<Option<PeatDocument>> {
let stored = self.document.read().unwrap();
Ok(stored.clone())
}
fn clear(&mut self) -> Result<()> {
let mut stored = self.document.write().unwrap();
*stored = None;
Ok(())
}
}
#[cfg(feature = "std")]
pub struct FileStore {
path: std::path::PathBuf,
}
#[cfg(feature = "std")]
impl FileStore {
pub fn new<P: Into<std::path::PathBuf>>(path: P) -> Self {
Self { path: path.into() }
}
pub fn path(&self) -> &std::path::Path {
&self.path
}
}
#[cfg(feature = "std")]
impl DocumentStore for FileStore {
fn save(&mut self, doc: &PeatDocument) -> Result<()> {
let data = doc.encode();
std::fs::write(&self.path, data).map_err(|e| {
crate::error::BleError::NotSupported(format!("Failed to write document: {}", e))
})?;
Ok(())
}
fn load(&self) -> Result<Option<PeatDocument>> {
match std::fs::read(&self.path) {
Ok(data) => Ok(PeatDocument::decode(&data)),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(e) => Err(crate::error::BleError::NotSupported(format!(
"Failed to read document: {}",
e
))),
}
}
fn clear(&mut self) -> Result<()> {
match std::fs::remove_file(&self.path) {
Ok(()) => Ok(()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(e) => Err(crate::error::BleError::NotSupported(format!(
"Failed to clear document: {}",
e
))),
}
}
}
#[cfg(feature = "std")]
pub struct SharedStore<S: DocumentStore> {
inner: Arc<RwLock<S>>,
}
#[cfg(feature = "std")]
impl<S: DocumentStore> SharedStore<S> {
pub fn new(store: S) -> Self {
Self {
inner: Arc::new(RwLock::new(store)),
}
}
}
#[cfg(feature = "std")]
impl<S: DocumentStore> Clone for SharedStore<S> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
#[cfg(feature = "std")]
impl<S: DocumentStore> DocumentStore for SharedStore<S> {
fn save(&mut self, doc: &PeatDocument) -> Result<()> {
let mut inner = self.inner.write().unwrap();
inner.save(doc)
}
fn load(&self) -> Result<Option<PeatDocument>> {
let inner = self.inner.read().unwrap();
inner.load()
}
fn clear(&mut self) -> Result<()> {
let mut inner = self.inner.write().unwrap();
inner.clear()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::NodeId;
#[test]
fn test_memory_store() {
let mut store = MemoryStore::new();
assert!(store.load().unwrap().is_none());
assert!(!store.has_document());
let doc = PeatDocument::new(NodeId::new(0x12345678));
store.save(&doc).unwrap();
let loaded = store.load().unwrap().unwrap();
assert_eq!(loaded.node_id.as_u32(), 0x12345678);
assert!(store.has_document());
store.clear().unwrap();
assert!(store.load().unwrap().is_none());
}
#[test]
fn test_file_store() {
let temp_dir = std::env::temp_dir();
let path = temp_dir.join("peat_test_doc.bin");
let _ = std::fs::remove_file(&path);
let mut store = FileStore::new(&path);
assert!(store.load().unwrap().is_none());
let mut doc = PeatDocument::new(NodeId::new(0xAABBCCDD));
doc.increment_counter();
store.save(&doc).unwrap();
let loaded = store.load().unwrap().unwrap();
assert_eq!(loaded.node_id.as_u32(), 0xAABBCCDD);
assert_eq!(loaded.counter.value(), 1);
store.clear().unwrap();
assert!(store.load().unwrap().is_none());
}
#[test]
fn test_shared_store() {
let store = MemoryStore::new();
let mut shared = SharedStore::new(store);
let doc = PeatDocument::new(NodeId::new(0x11111111));
shared.save(&doc).unwrap();
let shared2 = shared.clone();
let loaded = shared2.load().unwrap().unwrap();
assert_eq!(loaded.node_id.as_u32(), 0x11111111);
}
}