Change backends to do less ser-/deserialization

This commit changes the backends to do less de/ser, as it now stores the
Entry objects in the backend and does the de/serialization there.

This means the store does only serialize things once from json to toml
in the io backend.

See the diff of the documentation for more details.
This commit is contained in:
Matthias Beyer 2017-06-18 12:16:33 +02:00
parent 52011a59b2
commit 266311d743
5 changed files with 70 additions and 114 deletions

View file

@ -201,12 +201,6 @@ The `InMemoryFileAbstractionInstance` implementation is corrosponding to
the `InMemoryFileAbstraction` implementation - for the in-memory the `InMemoryFileAbstraction` implementation - for the in-memory
"filesystem". "filesystem".
The implementation of the `get_file_content()` function had to be
changed to return a `String` rather than a `&mut Read` because of
lifetime issues.
This change is store-internally and the API of the store itself was not
affected.
## The StdIo backend {#sec:thestore:backends:stdio} ## The StdIo backend {#sec:thestore:backends:stdio}
Sidenote: The name is "StdIo" because its main purpose is Stdin/Stdio, but it Sidenote: The name is "StdIo" because its main purpose is Stdin/Stdio, but it
@ -231,21 +225,11 @@ implementation are possible.
The following section assumes a JSON mapper. The following section assumes a JSON mapper.
The backends themselves do not know "header" and "content" - they know only The mapper reads the JSON, parses it (thanks serde!) and translates it to
blobs which live in pathes. a `Entry`, which is the in-memory representation of the files.
Indeed, this "backend" code does not serve "content" or "header" to the `Store` The `Entry` contains a `Header` part and a `Content` part.
implementation, but only a blob of bytes.
Anyways, the JSON-protocol for passing a store around _does_ know about content
and header (see @sec:thestore:backends:stdio:json for the JSON format).
So the mapper reads the JSON, parses it (thanks serde!) and translates it to
TOML, because TOML is the Store representation of a header.
But because the backend does not serve header and content, but only a blob,
this TOML is then translated (with the content of the respective file) to a
blob.
This is then made available to the store codebase. This is then made available to the store codebase.
This is complex and probably slow, we know.
To summarize what we do right now, lets have a look at the awesome ascii-art To summarize what we do right now, lets have a look at the awesome ascii-art
below: below:
@ -254,19 +238,16 @@ below:
libimag* libimag*
| |
v v
IO Mapper FS Store FS Mapper IO IO Mapper Store Mapper IO
+--+-------------+---------+--------+---------+--------------+--+ +--+---------+----------------+--------+--+
| | | | | | | | | | | | | |
JSON -> TOML -> String -> Entry -> String -> TOML -> JSON JSON -> Entry -> JSON
+ TOML + Header
+ Content + Content
``` ```
This is what gets translated where for one imag call with a stdio store backend. This is what gets translated where for one imag call with a stdio store backend.
The rationale behind this implementation is that this is the best implementation
we can have in a relatively short amount of time.
### The JSON Mapper {#sec:thestore:backends:stdio:json} ### The JSON Mapper {#sec:thestore:backends:stdio:json}
The JSON mapper maps JSON which is read from a source into a HashMap which The JSON mapper maps JSON which is read from a source into a HashMap which
@ -278,7 +259,7 @@ The strucure is as follows:
{ {
"version": "0.3.0", "version": "0.3.0",
"store": { "store": {
"/example": { "example": {
"header": { "header": {
"imag": { "imag": {
"version": "0.3.0", "version": "0.3.0",
@ -292,24 +273,14 @@ The strucure is as follows:
### TODO {#sec:thestore:backends:todo} ### TODO {#sec:thestore:backends:todo}
Of course, the above is not optimal. If you look at the version history of this file you will see that this
The TODO here is almost visible: Implement a proper backend where we do not need implementation has grown from something complex and probably slow to what we
to translate between types all the time. have today.
The first goal would be to reduce the above figure to: Still, there's one improvement we could make: abstract all the things away so
the `libimag*` crates handle the header without knowing whether it is JSON or
``` TOML.
libimag* With this, we would not even have to translate JSON to TOML anymore.
| We should measure whether this would have actually any performance impact before
v implementing it.
IO Mapper Store Mapper IO
+--+------+--------+-------+--+
| | | | | |
JSON -> Entry -> JSON
+ TOML
+ Content
```
and the second step would be to abstract all the things away so the `libimag*`
crates handle the header without knowing whether it is JSON or TOML.

View file

@ -19,8 +19,6 @@
use error::StoreError as SE; use error::StoreError as SE;
use error::StoreErrorKind as SEK; use error::StoreErrorKind as SEK;
use std::io::Read;
use std::io::Cursor;
use std::path::PathBuf; use std::path::PathBuf;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Mutex; use std::sync::Mutex;
@ -31,11 +29,10 @@ use libimagerror::into::IntoError;
use super::FileAbstraction; use super::FileAbstraction;
use super::FileAbstractionInstance; use super::FileAbstractionInstance;
use error::MapErrInto;
use store::Entry; use store::Entry;
use storeid::StoreId; use storeid::StoreId;
type Backend = Arc<Mutex<RefCell<HashMap<PathBuf, Cursor<Vec<u8>>>>>>; type Backend = Arc<Mutex<RefCell<HashMap<PathBuf, Entry>>>>;
/// `FileAbstraction` type, this is the Test version! /// `FileAbstraction` type, this is the Test version!
/// ///
@ -62,22 +59,16 @@ impl FileAbstractionInstance for InMemoryFileAbstractionInstance {
/** /**
* Get the mutable file behind a InMemoryFileAbstraction object * Get the mutable file behind a InMemoryFileAbstraction object
*/ */
fn get_file_content(&mut self, id: StoreId) -> Result<Entry, SE> { fn get_file_content(&mut self, _: StoreId) -> Result<Entry, SE> {
debug!("Getting lazy file: {:?}", self); debug!("Getting lazy file: {:?}", self);
let p = self.absent_path.clone(); let p = self.absent_path.clone();
match self.fs_abstraction.lock() { match self.fs_abstraction.lock() {
Ok(mut mtx) => { Ok(mut mtx) => {
mtx.get_mut() mtx.get_mut()
.get_mut(&p) .get(&p)
.cloned()
.ok_or(SEK::FileNotFound.into_error()) .ok_or(SEK::FileNotFound.into_error())
.and_then(|t| {
let mut s = String::new();
t.read_to_string(&mut s)
.map_err_into(SEK::IoError)
.map(|_| s)
.and_then(|s| Entry::from_str(id, &s))
})
} }
Err(_) => Err(SEK::LockError.into_error()) Err(_) => Err(SEK::LockError.into_error())
@ -85,20 +76,11 @@ impl FileAbstractionInstance for InMemoryFileAbstractionInstance {
} }
fn write_file_content(&mut self, buf: &Entry) -> Result<(), SE> { fn write_file_content(&mut self, buf: &Entry) -> Result<(), SE> {
let buf = buf.to_str().into_bytes();
match *self { match *self {
InMemoryFileAbstractionInstance { ref absent_path, .. } => { InMemoryFileAbstractionInstance { ref absent_path, .. } => {
let mut mtx = self.fs_abstraction.lock().expect("Locking Mutex failed"); let mut mtx = self.fs_abstraction.lock().expect("Locking Mutex failed");
let mut backend = mtx.get_mut(); let mut backend = mtx.get_mut();
let _ = backend.insert(absent_path.clone(), buf.clone());
if let Some(ref mut cur) = backend.get_mut(absent_path) {
let mut vec = cur.get_mut();
vec.clear();
vec.extend_from_slice(&buf);
return Ok(());
}
let vec = Vec::from(buf);
backend.insert(absent_path.clone(), Cursor::new(vec));
return Ok(()); return Ok(());
}, },
}; };

View file

@ -18,7 +18,6 @@
// //
use std::collections::HashMap; use std::collections::HashMap;
use std::io::Cursor;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
@ -29,16 +28,18 @@ use error::StoreErrorKind as SEK;
use error::MapErrInto; use error::MapErrInto;
use super::Mapper; use super::Mapper;
use store::Result; use store::Result;
use store::Entry;
use storeid::StoreId;
use libimagerror::into::IntoError; use libimagerror::into::IntoError;
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
struct Entry { struct BackendEntry {
header: serde_json::Value, header: serde_json::Value,
content: String, content: String,
} }
impl Entry { impl BackendEntry {
fn to_string(self) -> Result<String> { fn to_string(self) -> Result<String> {
toml::to_string(&self.header) toml::to_string(&self.header)
@ -55,7 +56,7 @@ impl Entry {
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
struct Document { struct Document {
version: String, version: String,
store: HashMap<PathBuf, Entry>, store: HashMap<PathBuf, BackendEntry>,
} }
pub struct JsonMapper; pub struct JsonMapper;
@ -69,7 +70,7 @@ impl JsonMapper {
} }
impl Mapper for JsonMapper { impl Mapper for JsonMapper {
fn read_to_fs<R: Read>(&self, r: &mut R, hm: &mut HashMap<PathBuf, Cursor<Vec<u8>>>) -> Result<()> { fn read_to_fs<R: Read>(&self, r: &mut R, hm: &mut HashMap<PathBuf, Entry>) -> Result<()> {
let mut document = { let mut document = {
let mut s = String::new(); let mut s = String::new();
try!(r.read_to_string(&mut s).map_err_into(SEK::IoError)); try!(r.read_to_string(&mut s).map_err_into(SEK::IoError));
@ -93,7 +94,11 @@ impl Mapper for JsonMapper {
for (key, val) in document.store.drain() { for (key, val) in document.store.drain() {
let res = val let res = val
.to_string() .to_string()
.map(|vals| hm.insert(key, Cursor::new(vals.into_bytes()))) .and_then(|vals| {
StoreId::new_baseless(key.clone())
.and_then(|id| Entry::from_str(id, &vals))
.map(|entry| hm.insert(key, entry))
})
.map(|_| ()); .map(|_| ());
let _ = try!(res); let _ = try!(res);
@ -102,43 +107,38 @@ impl Mapper for JsonMapper {
Ok(()) Ok(())
} }
fn fs_to_write<W: Write>(&self, hm: &mut HashMap<PathBuf, Cursor<Vec<u8>>>, out: &mut W) -> Result<()> { fn fs_to_write<W: Write>(&self, hm: &mut HashMap<PathBuf, Entry>, out: &mut W) -> Result<()> {
use util::entry_buffer_to_header_content; #[derive(Serialize)]
struct BackendEntry {
#[derive(Serialize, Deserialize)]
struct Entry {
header: ::toml::Value, header: ::toml::Value,
content: String, content: String,
} }
impl BackendEntry {
fn construct_from(e: Entry) -> BackendEntry {
BackendEntry {
header: e.get_header().clone(),
content: e.get_content().clone(),
}
}
}
#[derive(Serialize)] #[derive(Serialize)]
struct OutDocument { struct OutDocument {
version: String, version: String,
store: HashMap<PathBuf, Entry>, store: HashMap<PathBuf, BackendEntry>,
} }
let mut doc = OutDocument { let mut store = HashMap::new();
version: String::from(version!()),
store: HashMap::new(),
};
for (key, value) in hm.drain() { for (key, value) in hm.drain() {
let res = String::from_utf8(value.into_inner()) store.insert(key, BackendEntry::construct_from(value));
.map_err_into(SEK::IoError)
.and_then(|buf| entry_buffer_to_header_content(&buf))
.map(|(header, content)| {
let entry = Entry {
header: header,
content: content
};
doc.store.insert(key, entry);
})
.map(|_| ());
let _ = try!(res);
} }
let doc = OutDocument {
version: String::from(version!()),
store: store,
};
serde_json::to_string(&doc) serde_json::to_string(&doc)
.map_err_into(SEK::IoError) .map_err_into(SEK::IoError)
.and_then(|json| out.write(&json.into_bytes()).map_err_into(SEK::IoError)) .and_then(|json| out.write(&json.into_bytes()).map_err_into(SEK::IoError))
@ -158,7 +158,7 @@ mod test {
let json = r#" let json = r#"
{ "version": "0.3.0", { "version": "0.3.0",
"store": { "store": {
"/example": { "example": {
"header": { "header": {
"imag": { "imag": {
"version": "0.3.0" "version": "0.3.0"
@ -191,7 +191,10 @@ mod test {
version = "0.3.0" version = "0.3.0"
--- ---
hi there!"#; hi there!"#;
hm.insert(PathBuf::from("/example"), Cursor::new(String::from(content).into_bytes()));
let id = PathBuf::from("example");
let entry = Entry::from_str(id.clone(), content).unwrap();
hm.insert(id, entry);
hm hm
}; };
@ -202,7 +205,7 @@ hi there!"#;
{ {
"version": "0.3.0", "version": "0.3.0",
"store": { "store": {
"/example": { "example": {
"header": { "header": {
"imag": { "imag": {
"version": "0.3.0" "version": "0.3.0"

View file

@ -18,14 +18,14 @@
// //
use std::collections::HashMap; use std::collections::HashMap;
use std::io::Cursor;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use store::Result; use store::Result;
use store::Entry;
pub trait Mapper { pub trait Mapper {
fn read_to_fs<R: Read>(&self, &mut R, &mut HashMap<PathBuf, Cursor<Vec<u8>>>) -> Result<()>; fn read_to_fs<R: Read>(&self, &mut R, &mut HashMap<PathBuf, Entry>) -> Result<()>;
fn fs_to_write<W: Write>(&self, &mut HashMap<PathBuf, Cursor<Vec<u8>>>, &mut W) -> Result<()>; fn fs_to_write<W: Write>(&self, &mut HashMap<PathBuf, Entry>, &mut W) -> Result<()>;
} }
pub mod json; pub mod json;

View file

@ -23,7 +23,6 @@ use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::fmt::Error as FmtError; use std::fmt::Error as FmtError;
use std::fmt::Formatter; use std::fmt::Formatter;
use std::io::Cursor;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
@ -37,12 +36,13 @@ use error::StoreError as SE;
use super::FileAbstraction; use super::FileAbstraction;
use super::FileAbstractionInstance; use super::FileAbstractionInstance;
use super::InMemoryFileAbstraction; use super::InMemoryFileAbstraction;
use store::Entry;
pub mod mapper; pub mod mapper;
use self::mapper::Mapper; use self::mapper::Mapper;
// Because this is not exported in super::inmemory; // Because this is not exported in super::inmemory;
type Backend = Arc<Mutex<RefCell<HashMap<PathBuf, Cursor<Vec<u8>>>>>>; type Backend = Arc<Mutex<RefCell<HashMap<PathBuf, Entry>>>>;
pub struct StdIoFileAbstraction<W: Write, M: Mapper> { pub struct StdIoFileAbstraction<W: Write, M: Mapper> {
mapper: M, mapper: M,
@ -83,7 +83,7 @@ impl<W, M> StdIoFileAbstraction<W, M>
} }
pub fn backend(&self) -> &Backend { pub fn backend(&self) -> &Backend {
&self.mem.backend() self.mem.backend()
} }
} }