13 changed files with 403 additions and 63 deletions
@ -0,0 +1,23 @@ |
|||
use std::result; |
|||
|
|||
|
|||
#[derive(Debug, PartialEq, Eq)] |
|||
pub enum Error { |
|||
InvalidObjectIdSize, |
|||
InvalidObjectIdCharacter, |
|||
} |
|||
|
|||
|
|||
impl Error { |
|||
fn message(&self) -> String { |
|||
match self { |
|||
Error::InvalidObjectIdSize => |
|||
"invalid size for object id string".to_string(), |
|||
Error::InvalidObjectIdCharacter => |
|||
"invalid character in object id string".to_string(), |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
pub type Result<T> = result::Result<T, Error>; |
|||
@ -0,0 +1,5 @@ |
|||
pub mod error; |
|||
pub mod object_id; |
|||
pub mod object_type; |
|||
pub mod object; |
|||
pub mod object_db; |
|||
@ -0,0 +1,21 @@ |
|||
use super::object_id::ObjectId; |
|||
use super::object_type::ObjectType; |
|||
|
|||
|
|||
/// An object in an ObjectDb.
|
|||
///
|
|||
/// An Objects is fundamentaly an arbitrary blob of data. It has a unique identifier, a size and
|
|||
/// a type.
|
|||
#[derive(Debug)] |
|||
pub struct Object { |
|||
pub id: ObjectId, |
|||
pub otype: ObjectType, |
|||
pub size: usize, |
|||
} |
|||
|
|||
|
|||
impl Object { |
|||
fn new(object_id: ObjectId, otype: ObjectType, size: usize) -> Object { |
|||
Object { id: object_id, otype, size } |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
use std::io; |
|||
|
|||
use super::error::Result; |
|||
use super::object_id::ObjectId; |
|||
use super::object_type::ObjectType; |
|||
use super::object::Object; |
|||
|
|||
|
|||
pub trait ObjectDb { |
|||
fn object_exists(&self, oid: &ObjectId) -> Result<bool>; |
|||
fn get_object(&self, oid: &ObjectId) -> Result<Option<Object>>; |
|||
fn open_object(&self, oid: &ObjectId) -> Result<Box<dyn io::Read>>; |
|||
fn compute_object_id(&self, otype: ObjectType, reader: &dyn io::Read) -> Result<ObjectId>; |
|||
|
|||
fn add_object(&mut self, otype: ObjectType, reader: &dyn io::Read) -> Result<Object>; |
|||
fn delete_object(&mut self, oid: &ObjectId) -> Result<()>; |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
|
|||
|
|||
/// Describe the type of an Object.
|
|||
#[derive(Debug, Clone, PartialEq, Eq, Hash)] |
|||
pub struct ObjectType { |
|||
name: String, |
|||
} |
|||
|
|||
|
|||
impl ObjectType { |
|||
fn new(name: &str) -> ObjectType { |
|||
ObjectType { |
|||
name: name.to_string(), |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
|
|||
mod core; |
|||
mod object_db; |
|||
|
|||
fn main() { |
|||
println!("Hello, world!"); |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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")); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue