Add object reader & writer.

This commit is contained in:
2020-09-25 23:59:26 +02:00
parent 9bf0cf7b60
commit 82a9ddb603
13 changed files with 403 additions and 63 deletions

View File

@@ -11,3 +11,6 @@ serde = "1.0.106"
toml = "0.5.6"
uuid = { version = "0.8.1", features = ["serde", "v4"] }
tempfile = "3.1.0"
digest = "0.9.0"
sha2 = "0.9.1"
flate2 = "1.0.17"

View File

@@ -42,5 +42,10 @@ error_chain! {
description("Object id contains invalid character")
display("Object id contains invalid character")
}
InvalidObjectType(otype: [u8; 4]) {
description("Object has an invalid object type")
display("Object has an invalid object type {:?}", otype)
}
}
}

View File

@@ -30,9 +30,9 @@ pub struct ObjectId {
impl ObjectId {
pub fn new(id: Vec<u8>) -> ObjectId {
pub fn new(id: &[u8]) -> ObjectId {
ObjectId {
id
id: id.into(),
}
}
@@ -82,7 +82,7 @@ mod tests {
#[test]
fn object_id_display() {
let obj = ObjectId::new(vec![
let obj = ObjectId::new(&[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
]);
@@ -91,7 +91,7 @@ mod tests {
#[test]
fn object_id_debug() {
let obj = ObjectId::new(vec![
let obj = ObjectId::new(&[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
]);
@@ -101,7 +101,7 @@ mod tests {
#[test]
fn str_to_object_id() {
let obj = ObjectId::from_str("0001020304050607").unwrap();
let obj_ref = ObjectId::new(vec![
let obj_ref = ObjectId::new(&[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
]);

View File

@@ -14,63 +14,8 @@
// along with cdb. If not, see <https://www.gnu.org/licenses/>.
use std::path::{Path, PathBuf};
use crate::core::error::*;
use crate::core::object_id::ObjectId;
pub mod simple_db;
pub mod object;
const OBJECTS_FOLDER: &str = "objects";
#[derive(Debug)]
pub struct SimpleDb {
path: PathBuf,
}
impl SimpleDb {
pub fn new(path: &Path) -> Result<SimpleDb> {
Ok(SimpleDb {
path: path.into(),
})
}
pub fn has_object(&self, oid: &ObjectId) -> bool {
let obj_path = self.path_from_id(oid);
if let Ok(metadata) = std::fs::metadata(obj_path) {
metadata.is_file()
}
else {
false
}
}
fn path_from_id(&self, oid: &ObjectId) -> PathBuf {
let oid_str = oid.to_string();
let mut path = self.path.clone();
path.push(OBJECTS_FOLDER);
path.push(oid_str.get(..4).unwrap()); // unwrap is ok here because we know oid_str is a
path.push(oid_str.get(4..).unwrap()); // hexadecimal number (i.e. ascii only).
path
}
}
#[cfg(test)]
mod tests {
use super::ObjectId;
use super::SimpleDb;
#[test]
fn simple_db_path_from_id() {
let db = SimpleDb::new(std::path::Path::new(".bsv")).unwrap();
let oid = ObjectId::from_str("0001020304050607").unwrap();
let path = db.path_from_id(&oid);
assert_eq!(path, std::path::PathBuf::from(".bsv/objects/0001/020304050607"));
}
}
pub use simple_db::SimpleDb;

View File

@@ -0,0 +1,161 @@
// This file is part of bsv.
//
// bsv is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// cdb is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
// more details.
//
// You should have received a copy of the Affero GNU General Public License
// along with cdb. If not, see <https://www.gnu.org/licenses/>.
extern crate digest;
extern crate sha2;
extern crate flate2;
use std::io::{Read, Write, copy};
use digest::Digest;
use crate::core::error::*;
use crate::core::ObjectId;
type ObjectType = [u8; 4];
pub const TYPE_BLOB: &ObjectType = b"blob";
pub const TYPE_TREE: &ObjectType = b"tree";
pub struct ObjectWriter<'a, W: Write, D: Digest> {
writer: &'a mut W,
digest: D,
}
impl<'a, W: Write, D: Digest> ObjectWriter<'a, W, D> {
pub fn new(writer: &'a mut W, otype: &ObjectType, size: u64)
-> Result<ObjectWriter<'a, W, D>> {
let mut digest = D::new();
digest.update(otype);
digest.update(&size.to_be_bytes());
writer.write_all(otype)?;
writer.write_all(&size.to_be_bytes())?;
Ok(ObjectWriter {
writer,
digest,
})
}
pub fn close(self) -> Result<ObjectId> {
Ok(ObjectId::new(&self.digest.finalize()))
}
}
impl<'a, W: Write, D: Digest> Write for ObjectWriter<'a, W, D> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let size = self.writer.write(buf)?;
self.digest.update(&buf[..size]);
Ok(size)
}
fn flush(&mut self) -> std::io::Result<()> {
self.writer.flush()
}
}
pub fn write_object<'a, R, W>(reader: &mut R, writer: &'a mut W,
otype: &ObjectType, size: u64)
-> Result<ObjectId> where
R: Read,
W: Write {
let mut owriter: ObjectWriter<W, sha2::Sha224>
= ObjectWriter::new(writer, otype, size)?;
copy(reader, &mut owriter)?;
owriter.close()
}
pub struct ObjectReader<R: Read> {
reader: R,
}
impl<R: Read> ObjectReader<R> {
pub fn new(mut reader: R)
-> Result<(ObjectType, u64, ObjectReader<R>)> {
let mut buffer = [0u8; 12];
reader.read_exact(&mut buffer)?;
let otype = {
let mut otype = [0; 4];
otype.copy_from_slice(&buffer[0..4]);
otype
};
let size = {
let mut size_bytes = [0; 8];
size_bytes.copy_from_slice(&buffer[4..12]);
u64::from_be_bytes(size_bytes)
};
Ok((
otype,
size,
ObjectReader {
reader,
}
))
}
pub fn close(self) -> Result<()> {
Ok(())
}
}
impl<R: Read> Read for ObjectReader<R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.reader.read(buf)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn object_read_write() {
use std::io::{Cursor, Seek, SeekFrom};
let payload = b"Hello World!";
let mut source = Cursor::new(payload);
let mut fake_file = Cursor::new(vec![]);
let mut output = vec![];
write_object(&mut source, &mut fake_file, TYPE_BLOB, payload.len() as u64).unwrap();
fake_file.seek(SeekFrom::Start(0)).unwrap();
let (otype, size, mut reader) = ObjectReader::new(fake_file).unwrap();
let read_size = reader.read_to_end(&mut output).unwrap();
assert_eq!(otype, *TYPE_BLOB);
assert_eq!(size, payload.len() as u64);
assert_eq!(read_size, payload.len());
assert_eq!(output, payload);
}
}

View File

@@ -0,0 +1,76 @@
// This file is part of bsv.
//
// bsv is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// cdb is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
// more details.
//
// You should have received a copy of the Affero GNU General Public License
// along with cdb. If not, see <https://www.gnu.org/licenses/>.
use std::path::{Path, PathBuf};
use crate::core::error::*;
use crate::core::ObjectId;
const OBJECTS_FOLDER: &str = "objects";
#[derive(Debug)]
pub struct SimpleDb {
path: PathBuf,
}
impl SimpleDb {
pub fn new(path: &Path) -> Result<SimpleDb> {
Ok(SimpleDb {
path: path.into(),
})
}
pub fn has_object(&self, oid: &ObjectId) -> bool {
let obj_path = self.path_from_id(oid);
if let Ok(metadata) = std::fs::metadata(obj_path) {
metadata.is_file()
}
else {
false
}
}
fn path_from_id(&self, oid: &ObjectId) -> PathBuf {
let oid_str = oid.to_string();
let mut path = self.path.clone();
path.push(OBJECTS_FOLDER);
path.push(oid_str.get(..4).unwrap()); // unwrap is ok here because we know oid_str is a
path.push(oid_str.get(4..).unwrap()); // hexadecimal number (i.e. ascii only).
path
}
}
#[cfg(test)]
mod tests {
use super::ObjectId;
use super::SimpleDb;
#[test]
fn simple_db_path_from_id() {
let db = SimpleDb::new(std::path::Path::new(".bsv")).unwrap();
let oid = ObjectId::from_str("0001020304050607").unwrap();
let path = db.path_from_id(&oid);
assert_eq!(path, std::path::PathBuf::from(".bsv/objects/0001/020304050607"));
}
}