//! A persistence layer for lockchain vaults based on files //! //! This crate provides a filesystem backend //! which relies on keeping records in discrete files //! and folder structures. //! //! All further documentation can be found in `FileVault` #![feature(non_modrs_mods)] extern crate lockchain_core as lcc; extern crate semver; extern crate toml; #[macro_use] extern crate serde_derive; extern crate serde; use lcc::traits::{Body, LoadRecord, Vault}; use lcc::{ errors::VaultError, users::{Access, Token, UserStore}, Generator, Header, MetaDomain, Payload, Record, VaultMetadata, }; use std::collections::HashMap; mod config; mod create; mod fs; mod load; mod userstore; mod utils; pub use config::{ConfigError, VaultConfig}; use fs::{FileType, Filesystem}; use userstore::UserStoreMapper; /// Persistence mapper to a folder and file structure /// /// This implementation tries to be as efficient /// as possible, however please note that it is /// dependant on filesystem operations and is /// not suited for high-performance applications! /// /// --- /// /// Implements the `Vault` API in full, /// replicating all functionality in memory /// while providing async operations on-disk. /// /// Requests on files are debounced! /// /// The internal layout should not be assumed /// and isn't stabilised with the crate version /// (i.e. minor crate bumps can break vault compatibility /// as long as they remain API compatible). /// /// The version of a vault is written in it's coniguration /// which can be read via `json-compat` shims, /// in case the layout and scheme ever changes. /// /// The vault folder is safe to copy around – /// all vault metadata is kept inside it. pub struct FileVault { /// A representation of the cached vault config config: VaultConfig, /// Filesystem wrapper utility fs: Filesystem, /// A userstore utility derived from Metadata users: UserStoreMapper, /// A mapping of loaded records records: HashMap>, /// An index of all existing headers headers: HashMap, /// A map of all metadata files metadata: HashMap, } impl LoadRecord for FileVault {} impl Vault for FileVault { fn new(gen: Generator) -> Result>, VaultError> { Self::create(gen).map(|s| Box::new(s)) } fn load(name: &str, location: &str) -> Result, VaultError> { Self::load(name, location).map(|s| Box::new(s)) } fn create_user( &mut self, token: Token, username: &str, secret: &str, access: Vec, ) -> Result<(), ()> { unimplemented!() } fn delete_user(&mut self, token: Token, username: &str) { unimplemented!() } fn authenticate(&mut self, username: &str, secret: &str) -> Token { unimplemented!() } fn deauthenticate(&mut self, username: &str, _: Token) { unimplemented!() } fn metadata(&self) -> VaultMetadata { unimplemented!() } /// Caches all files from disk to memory fn fetch(&mut self) { self.records.clear(); self.metadata.clear(); self.fs .fetch::>(FileType::Record) .unwrap() .into_iter() .map(|rec| (rec.header.name.clone(), rec)) .for_each(|x| { self.records.insert(x.0, x.1); }); self.fs .fetch::(FileType::Metadata) .unwrap() .into_iter() .map(|rec| (rec.name().into(), rec)) .for_each(|x| { self.metadata.insert(x.0, x.1); }); } /// Make sure a single record is loaded fn pull(&mut self, name: &str) { self.records.remove(name); self.records.insert( name.to_owned(), self.fs.pull::>(FileType::Record, name).unwrap(), ); } fn sync(&mut self) { self.fs .sync::>(&self.records, FileType::Record) .unwrap(); self.fs .sync::(&self.metadata, FileType::Metadata) .unwrap(); } fn get_record(&self, name: &str) -> Option<&Record> { self.records.get(name) } fn contains(&self, name: &str) -> bool { self.records.contains_key(name) } fn add_record(&mut self, key: &str, category: &str, tags: Vec<&str>) { self.records .insert(key.to_owned(), Record::new(key, category, tags)); } fn delete_record(&mut self, record: &str) -> Option> { self.records.remove(record) } fn add_data(&mut self, record: &str, key: &str, data: Payload) -> Option<()> { self.records.get_mut(record)?.add_data(key, data) } fn get_data(&self, record: &str, key: &str) -> Option<&Payload> { self.records.get(record)?.get_data(key) } fn meta_add_domain(&mut self, domain: &str) -> Option<()> { if self.metadata.contains_key(domain) { None } else { self.metadata.insert(domain.into(), MetaDomain::new(domain)); Some(()) } } fn meta_pull_domain(&self, domain: &str) -> Option<&MetaDomain> { self.metadata.get(domain) } fn meta_push_domain(&mut self, domain: MetaDomain) -> Option<()> { self.metadata .insert(domain.name().into(), domain) .map_or((), |_| ()) // We don't care about `None` .into() } fn meta_set(&mut self, domain: &str, name: &str, data: Payload) -> Option<()> { self.metadata.get_mut(domain)?.set_field(name, data) } fn meta_get(&mut self, domain: &str, name: &str) -> Option { Some(self.metadata.get(domain)?.get_field(name)?.clone()) } fn meta_exists(&self, domain: &str) -> bool { self.metadata.contains_key(domain) } }