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