|
|
|
@ -22,6 +22,9 @@ use crate::data::Object; |
|
|
|
|
use crate::update::{UpdateObj, UpsertValue}; |
|
|
|
|
pub use data::TypedValue; |
|
|
|
|
pub use model::DataType; |
|
|
|
|
use std::path::{PathBuf, Path}; |
|
|
|
|
use std::fs::OpenOptions; |
|
|
|
|
use std::io::{BufReader, BufWriter}; |
|
|
|
|
|
|
|
|
|
mod cool; |
|
|
|
|
pub mod data; |
|
|
|
@ -34,7 +37,7 @@ mod serde_map_as_list; |
|
|
|
|
#[cfg(test)] |
|
|
|
|
mod tests; |
|
|
|
|
|
|
|
|
|
/// Stupid storage with no persistence
|
|
|
|
|
/// Stupid storage with naive inefficient file persistence
|
|
|
|
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)] |
|
|
|
|
pub struct Storage { |
|
|
|
|
#[serde(with = "serde_map_as_list")] |
|
|
|
@ -50,14 +53,46 @@ pub struct Storage { |
|
|
|
|
relations: HashMap<ID, data::Relation>, |
|
|
|
|
#[serde(with = "serde_map_as_list")] |
|
|
|
|
values: HashMap<ID, data::Value>, |
|
|
|
|
|
|
|
|
|
#[serde(skip)] |
|
|
|
|
opts: StoreOpts, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)] |
|
|
|
|
pub struct StoreOpts { |
|
|
|
|
file : Option<PathBuf>, |
|
|
|
|
file_format: FileEncoding |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl Default for StoreOpts { |
|
|
|
|
fn default() -> Self { |
|
|
|
|
Self { |
|
|
|
|
file: None, |
|
|
|
|
file_format: FileEncoding::JSON |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[derive(Debug,Clone,Copy)] |
|
|
|
|
pub enum FileEncoding { |
|
|
|
|
JSON, |
|
|
|
|
BINCODE, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[derive(Debug, Error)] |
|
|
|
|
#[derive(Debug,Error)] |
|
|
|
|
pub enum StorageError { |
|
|
|
|
#[error("Referenced {0} does not exist")] |
|
|
|
|
NotExist(Cow<'static, str>), |
|
|
|
|
#[error("{0}")] |
|
|
|
|
ConstraintViolation(Cow<'static, str>), |
|
|
|
|
#[error("Persistence not configured!")] |
|
|
|
|
NotPersistent, |
|
|
|
|
#[error(transparent)] |
|
|
|
|
IO(#[from] std::io::Error), |
|
|
|
|
#[error(transparent)] |
|
|
|
|
SerdeJson(#[from] serde_json::Error), |
|
|
|
|
#[error(transparent)] |
|
|
|
|
Bincode(#[from] bincode::Error), |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl Storage { |
|
|
|
@ -66,6 +101,90 @@ impl Storage { |
|
|
|
|
Self::default() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn new_json(file : impl AsRef<Path>) -> Self { |
|
|
|
|
let mut s = Self::new(); |
|
|
|
|
s.opts.file_format = FileEncoding::JSON; |
|
|
|
|
s.opts.file = Some(file.as_ref().to_path_buf()); |
|
|
|
|
s |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn new_bincode(file : impl AsRef<Path>) -> Self { |
|
|
|
|
let mut s = Self::new(); |
|
|
|
|
s.opts.file_format = FileEncoding::BINCODE; |
|
|
|
|
s.opts.file = Some(file.as_ref().to_path_buf()); |
|
|
|
|
s |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn set_file(&mut self, file : impl AsRef<Path>, format : FileEncoding) { |
|
|
|
|
self.opts.file_format = format; |
|
|
|
|
self.opts.file = Some(file.as_ref().to_path_buf()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn unset_file(&mut self) { |
|
|
|
|
self.opts.file = None; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn load(&mut self) -> Result<(), StorageError> { |
|
|
|
|
match &self.opts.file { |
|
|
|
|
None => { |
|
|
|
|
return Err(StorageError::NotPersistent); |
|
|
|
|
} |
|
|
|
|
Some(path) => { |
|
|
|
|
debug!("Load from: {}", path.display()); |
|
|
|
|
|
|
|
|
|
if !path.exists() { |
|
|
|
|
warn!("File does not exist, skip load."); |
|
|
|
|
return Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let f = OpenOptions::new().read(true).open(&path)?; |
|
|
|
|
|
|
|
|
|
let reader = BufReader::new(f); |
|
|
|
|
|
|
|
|
|
let parsed : Self = match self.opts.file_format { |
|
|
|
|
FileEncoding::JSON => { |
|
|
|
|
serde_json::from_reader(reader)? |
|
|
|
|
} |
|
|
|
|
FileEncoding::BINCODE => { |
|
|
|
|
bincode::deserialize_from(reader)? |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
let opts = std::mem::replace(&mut self.opts, StoreOpts::default()); |
|
|
|
|
*self = parsed; |
|
|
|
|
self.opts = opts; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn persist(&mut self) -> Result<(), StorageError> { |
|
|
|
|
match &self.opts.file { |
|
|
|
|
None => { |
|
|
|
|
warn!("Store is not persistent!"); |
|
|
|
|
//return Err(StorageError::NotPersistent);
|
|
|
|
|
} |
|
|
|
|
Some(path) => { |
|
|
|
|
debug!("Persist to: {}", path.display()); |
|
|
|
|
|
|
|
|
|
let f = OpenOptions::new().write(true).create(true).truncate(true).open(&path)?; |
|
|
|
|
let writer = BufWriter::new(f); |
|
|
|
|
|
|
|
|
|
match self.opts.file_format { |
|
|
|
|
FileEncoding::JSON => { |
|
|
|
|
serde_json::to_writer(writer, self)?; |
|
|
|
|
} |
|
|
|
|
FileEncoding::BINCODE => { |
|
|
|
|
bincode::serialize_into(writer, self)? |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Define a data object
|
|
|
|
|
pub fn define_object(&mut self, mut tpl: model::ObjectModel) -> Result<ID, StorageError> { |
|
|
|
|
if tpl.name.is_empty() { |
|
|
|
|