aboutsummaryrefslogtreecommitdiff
path: root/lockchain-files/src
diff options
context:
space:
mode:
authorKatharina Fey <kookie@spacekookie.de>2018-09-04 16:29:03 +0200
committerKatharina Fey <kookie@spacekookie.de>2018-09-04 16:29:03 +0200
commit0b3e6767864d3c1c7d698d3d4f383de0ae4084b6 (patch)
tree3a41e5739a0fefda3d2eec43800676a4ec078137 /lockchain-files/src
parent15182c1a4fd14aeda13d2d82a2294e8c7eb3a49d (diff)
Cleaning up the core a bit. Adding better (any) config handling to files
Diffstat (limited to 'lockchain-files/src')
-rw-r--r--lockchain-files/src/config.rs85
-rw-r--r--lockchain-files/src/fs.rs81
-rw-r--r--lockchain-files/src/lib.rs100
-rw-r--r--lockchain-files/src/utils.rs26
4 files changed, 241 insertions, 51 deletions
diff --git a/lockchain-files/src/config.rs b/lockchain-files/src/config.rs
new file mode 100644
index 0000000..b2d311a
--- /dev/null
+++ b/lockchain-files/src/config.rs
@@ -0,0 +1,85 @@
+use std::error::Error;
+use std::fmt;
+use std::time::SystemTime;
+use std::{fs::File, path::PathBuf, io};
+
+use semver::Version;
+use toml;
+use utils::FileToString;
+
+/// A set of errors around `lockchain-files` configs
+#[derive(Debug)]
+pub enum ConfigError {
+ IncompatibleVersion(String, String),
+ ConfigCorrupted,
+}
+
+impl fmt::Display for ConfigError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::ConfigError::*;
+ write!(
+ f,
+ "{}",
+ match self {
+ IncompatibleVersion(f, l) => {
+ format!("Version '{}' is incompatible with library '{}'", f, l)
+ }
+ ConfigCorrupted => "Configuration file was corrupted!".into(),
+ }
+ )
+ }
+}
+
+impl Error for ConfigError {}
+
+/// The configuration describing a file vault
+#[derive(Serialize, Deserialize)]
+pub struct VaultConfig {
+ /// A semver conforming version string
+ pub version: String,
+ pub created_at: SystemTime,
+ pub modified_at: SystemTime,
+}
+
+impl VaultConfig {
+ pub fn new() -> Self {
+ Self {
+ version: "0.1".into(),
+ created_at: SystemTime::now(),
+ modified_at: SystemTime::now(),
+ }
+ }
+
+ pub fn save(&self) -> Result<(), io::Error> {
+
+ Ok(())
+ }
+
+ /// Attempts to load a configuration – returning detailed errors
+ pub fn load(vault: &PathBuf) -> Result<Self, ConfigError> {
+ let mut cfg_path = vault.clone();
+ cfg_path.push("config.toml");
+
+ let cfg: VaultConfig = match File::open(cfg_path.as_path()) {
+ Ok(mut f) => match f.get_string() {
+ Ok(s) => match toml::from_str(&s) {
+ Ok(c) => c,
+ Err(_) => return Err(ConfigError::ConfigCorrupted),
+ },
+ Err(_) => return Err(ConfigError::ConfigCorrupted),
+ },
+ Err(_) => return Err(ConfigError::ConfigCorrupted),
+ };
+
+ let version = match Version::parse(&cfg.version) {
+ Ok(v) => v,
+ Err(_) => return Err(ConfigError::ConfigCorrupted),
+ };
+
+ if version != Version::parse("0.1").unwrap() {
+ Err(ConfigError::IncompatibleVersion(cfg.version, "0.1".into()))
+ } else {
+ Ok(cfg)
+ }
+ }
+}
diff --git a/lockchain-files/src/fs.rs b/lockchain-files/src/fs.rs
index be5ccba..1aa57bc 100644
--- a/lockchain-files/src/fs.rs
+++ b/lockchain-files/src/fs.rs
@@ -1,10 +1,10 @@
//! Filesystem abstraction for various data types
-//!
+//!
//! All operations return io::Result<()> to indicate errors
//! and functions that have multiple file endpoints will return
//! a folded error list to indicate which ops were successful
//! and which failed.
-//!
+//!
//! There is also a `From<Vec<?>> for Result<?>` implementation
//! which will return either `Ok(())` or the first error in the list
//! of operations.
@@ -13,20 +13,23 @@ use lcc::traits::AutoEncoder;
use std::collections::HashMap;
use std::error::Error;
-use std::io::{self, Read, Write};
+use std::io::Write;
use std::{
- fs::{self, File, OpenOptions as OO}, path::PathBuf,
+ fs::{self, File, OpenOptions as OO},
+ path::PathBuf,
};
+use utils::FileToString;
+
#[derive(Debug)]
pub struct Filesystem {
- name: String,
- path: String,
- root: PathBuf,
+ pub name: String,
+ pub path: String,
+ pub root: PathBuf,
}
/// A switching enum to determine what type of file to load
-
+#[allow(dead_code)]
pub enum FileType {
/// A data record file
Record,
@@ -34,6 +37,8 @@ pub enum FileType {
Metadata,
/// A simple checksum file
Checksum,
+ #[doc(hidden)]
+ __NonExhaustive,
}
/// Construct a file ending for a specific match result
@@ -42,18 +47,22 @@ macro_rules! file_ending {
match $type {
FileType::Record => "record",
FileType::Metadata => "meta",
+ FileType::Checksum => "sum",
_ => "dat",
}
};
}
impl Filesystem {
- pub fn create(path: &str, name: &str) -> Filesystem {
+ /// Create a new filesystem representation
+ ///
+ /// This function does _not_ touch the disk!
+ pub fn new(path: &str, name: &str) -> Self {
let mut buffer = PathBuf::new();
buffer.push(path);
buffer.push(format!("{}.vault", name));
- Filesystem {
+ Self {
name: name.to_owned(),
path: path.to_owned(),
root: buffer,
@@ -74,27 +83,29 @@ impl Filesystem {
Ok(fs::read_dir(match types {
FileType::Record => self.root.join("records"),
FileType::Metadata => self.root.join("metadata"),
- _ => self.root.join("."),
+ _ => self.root.clone(),
})?.into_iter()
- .filter_map(|r| r.ok())
- .filter(|f| match f.file_type() {
- Ok(vf) => vf.is_file(),
- _ => false,
- })
- .map(|de| de.path())
- .filter_map(|p| p.into_os_string().into_string().ok())
- .filter_map(|s| File::open(s).ok())
- .filter_map(|mut f| f.get_string().ok())
- .filter_map(|s| T::decode(&s).ok())
- .collect())
+ .filter_map(|r| r.ok())
+ .filter(|f| match f.file_type() {
+ Ok(vf) => vf.is_file(),
+ _ => false,
+ }).map(|de| de.path())
+ .filter_map(|p| p.into_os_string().into_string().ok())
+ .filter_map(|s| File::open(s).ok())
+ .filter_map(|mut f| f.get_string().ok())
+ .filter_map(|s| T::decode(&s).ok())
+ .collect())
}
+ /// Retrieve a single record from the cached vault
pub fn pull<T: AutoEncoder>(&self, types: FileType, id: &str) -> Result<T, Box<Error>> {
Ok(T::decode(
- &File::open(self.root.join(&format!("{}.{}", id, file_ending!(types))))?.get_string()?,
+ &File::open(self.root.join(&format!("{}.{}", id, file_ending!(types))))?
+ .get_string()?,
)?)
}
+ /// Respond to a sync request
pub fn sync<T>(&self, data: &HashMap<String, T>, types: FileType) -> Result<(), Box<Error>>
where
T: AutoEncoder,
@@ -110,13 +121,11 @@ impl Filesystem {
}.join(format!("{}.{}", k, file_ending!(types))),
v,
)
- })
- .filter(|(_, v)| v.is_some())
+ }).filter(|(_, v)| v.is_some())
.map(|(k, v)| (k, v.unwrap()))
.map(|(path, data): (PathBuf, String)| {
(OO::new().create(true).write(true).open(path), data)
- })
- .filter(|(path, _)| path.is_ok())
+ }).filter(|(path, _)| path.is_ok())
.map(|(file, data)| (file.unwrap(), data))
.for_each(|(mut file, data)| {
file.write_all(data.as_bytes())
@@ -126,21 +135,3 @@ impl Filesystem {
Ok(())
}
}
-
-/// A utility trait to read the conents from a file in
-/// a single line.
-pub trait FileToString {
- /// Read the file contents into a string without any
- /// error handling.
- fn get_string(&mut self) -> Result<String, io::Error>;
-}
-
-impl FileToString for File {
- fn get_string(&mut self) -> Result<String, io::Error> {
- let mut s = String::new();
- return match self.read_to_string(&mut s) {
- Ok(_) => Ok(s),
- Err(e) => Err(e),
- };
- }
-}
diff --git a/lockchain-files/src/lib.rs b/lockchain-files/src/lib.rs
index afa3a87..965f669 100644
--- a/lockchain-files/src/lib.rs
+++ b/lockchain-files/src/lib.rs
@@ -1,18 +1,87 @@
-//! A module that enables file management for vaults
+//! 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.
//!
+//! This is great if a vault needs to be easily syncable
+//! or indexable by another tool.
+//! No clear-text secrets are ever written to disk.
+//! But can sometimes be held in memory cache
+//! for a period of time.
+//!
+//! This backend is comparibly slow
+//! and should be avoided
+//! for performance critical applications.
+//! For such applications
+//! the blockstore is much more suited
+//! which represents a vault
+//! in a binary blob
+//! independant of the used filesystem.
+//!
+//! Part of the performance problems
+//! comes from locking the entire vault
+//! when doing operations,
+//! meaning that only
+//! one instance
+//! of a lockchain library
+//! can operate on it
+//! at the time
+//!
+//! ```
+//! my_vault/
+//! config.toml
+//! Lockfile
+//! metadata/
+//! userstore.meta
+//! registry.meta
+//! records/
+//! <base64 hash 1>.rec
+//! <base64 hash 2>.rec
+//! <base64 hash 3>.rec
+//! hashsums/
+//! <base64 hash 1>.sum
+//! <base64 hash 2>.sum
+//! <base64 hash 3>.sum
+//! ```
#![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::{users::Token, MetaDomain, Payload, Record, VaultMetadata};
use std::collections::HashMap;
mod fs;
-use fs::{FileType, Filesystem};
+mod utils;
+mod config;
-/// Represents a vault on disk
+use fs::{FileType, Filesystem};
+pub use config::{VaultConfig, ConfigError};
+
+
+/// Persistence mapper to a folder and file structure
+///
+/// Implements the `Vault` API in full,
+/// replicating all functionality in memory
+/// and never writing clear text data to disk.
+///
+/// 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 won't change – ever).
+///
+/// The vault folder is safe to copy around –
+/// all vault metadata is kept inside it.
#[derive(Debug)]
pub struct DataVault<T: Body> {
meta_info: (String, String),
@@ -27,6 +96,15 @@ impl<T: Body> DataVault<T> {
self.fs.scaffold();
self
}
+
+ fn load(mut self) -> Option<Box<Self>> {
+ let config = match VaultConfig::load(&self.fs.root) {
+ Ok(cfg) => cfg,
+ _ => return None,
+ };
+
+ Some(Box::new(self))
+ }
}
impl<T: Body> LoadRecord<T> for DataVault<T> {}
@@ -37,12 +115,22 @@ impl<T: Body> Vault<T> for DataVault<T> {
meta_info: (name.into(), location.into()),
records: HashMap::new(),
metadata: HashMap::new(),
- fs: Filesystem::create(location, name),
+ fs: Filesystem::new(location, name),
}.initialize()
}
- fn load(name: &str, location: &str) -> Self {
- unimplemented!()
+ // Checking if a vault exists is basically checking it's config
+ // against the compatible version of this library.
+ //
+ // If it's compatible we can open the vault into memory
+ // (loading all required paths into the struct), then return it
+ fn load(name: &str, location: &str) -> Option<Box<Self>> {
+ Self {
+ meta_info: (name.into(), location.into()),
+ records: HashMap::new(),
+ metadata: HashMap::new(),
+ fs: Filesystem::new(location, name),
+ }.load()
}
fn authenticate(&mut self, username: &str, secret: &str) -> Token {
diff --git a/lockchain-files/src/utils.rs b/lockchain-files/src/utils.rs
new file mode 100644
index 0000000..c724d32
--- /dev/null
+++ b/lockchain-files/src/utils.rs
@@ -0,0 +1,26 @@
+//! Small utility module for file operations
+
+use std::io::{self, Read};
+use std::fs::File;
+
+pub fn check_config() {
+
+}
+
+/// A utility trait to read the conents from a file in
+/// a single line.
+pub trait FileToString {
+ /// Read the file contents into a string without any
+ /// error handling.
+ fn get_string(&mut self) -> Result<String, io::Error>;
+}
+
+impl FileToString for File {
+ fn get_string(&mut self) -> Result<String, io::Error> {
+ let mut s = String::new();
+ return match self.read_to_string(&mut s) {
+ Ok(_) => Ok(s),
+ Err(e) => Err(e),
+ };
+ }
+}