Add object reader & writer.
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
161
libbsv/src/simple_db/object.rs
Normal file
161
libbsv/src/simple_db/object.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
76
libbsv/src/simple_db/simple_db.rs
Normal file
76
libbsv/src/simple_db/simple_db.rs
Normal 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"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user