|
|
@ -18,7 +18,7 @@ extern crate digest; |
|
|
extern crate sha2; |
|
|
extern crate sha2; |
|
|
extern crate flate2; |
|
|
extern crate flate2; |
|
|
|
|
|
|
|
|
use std::io::{Read, Write, copy}; |
|
|
use std::io::{Read, Seek, SeekFrom, Write, copy, sink}; |
|
|
|
|
|
|
|
|
use digest::Digest; |
|
|
use digest::Digest; |
|
|
|
|
|
|
|
|
@ -32,15 +32,17 @@ use crate::core::error::*; |
|
|
use crate::core::ObjectId; |
|
|
use crate::core::ObjectId; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type ObjectType = [u8; 4]; |
|
|
pub type ObjectType = [u8; 4]; |
|
|
|
|
|
|
|
|
pub const TYPE_BLOB: &ObjectType = b"blob"; |
|
|
pub const OTYPE_BLOB: &ObjectType = b"blob"; |
|
|
pub const TYPE_TREE: &ObjectType = b"tree"; |
|
|
pub const OTYPE_TREE: &ObjectType = b"tree"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub struct ObjectWriter<W: Write, D: Digest> { |
|
|
pub struct ObjectWriter<W: Write, D: Digest> { |
|
|
writer: GzEncoder<W>, |
|
|
writer: GzEncoder<W>, |
|
|
digest: D, |
|
|
digest: D, |
|
|
|
|
|
size: u64, |
|
|
|
|
|
written_size: u64, |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -61,16 +63,24 @@ impl<W: Write, D: Digest> ObjectWriter<W, D> { |
|
|
Ok(ObjectWriter { |
|
|
Ok(ObjectWriter { |
|
|
writer: zwriter, |
|
|
writer: zwriter, |
|
|
digest, |
|
|
digest, |
|
|
|
|
|
size, |
|
|
|
|
|
written_size: 0, |
|
|
}) |
|
|
}) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
pub fn finish(mut self) -> Result<(ObjectId, W)> { |
|
|
pub fn finish(mut self) -> Result<(ObjectId, W)> { |
|
|
self.writer.try_finish()?; |
|
|
self.writer.try_finish()?; |
|
|
|
|
|
|
|
|
|
|
|
if self.written_size == self.size { |
|
|
Ok(( |
|
|
Ok(( |
|
|
ObjectId::new(&self.digest.finalize()), |
|
|
ObjectId::new(&self.digest.finalize()), |
|
|
self.writer.finish()?, |
|
|
self.writer.finish()?, |
|
|
)) |
|
|
)) |
|
|
} |
|
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
Err(ErrorKind::MismatchingObjectSize(self.written_size, self.size).into()) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -78,6 +88,7 @@ impl<W: Write, D: Digest> Write for ObjectWriter<W, D> { |
|
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { |
|
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { |
|
|
let size = self.writer.write(buf)?; |
|
|
let size = self.writer.write(buf)?; |
|
|
self.digest.update(&buf[..size]); |
|
|
self.digest.update(&buf[..size]); |
|
|
|
|
|
self.written_size += size as u64; |
|
|
Ok(size) |
|
|
Ok(size) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -87,28 +98,16 @@ impl<W: Write, D: Digest> Write for ObjectWriter<W, D> { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn write_object<R, W>(reader: &mut R, writer: W, |
|
|
|
|
|
otype: &ObjectType, size: u64) |
|
|
|
|
|
-> Result<(ObjectId, W)> where |
|
|
|
|
|
R: Read, |
|
|
|
|
|
W: Write { |
|
|
|
|
|
let mut owriter: ObjectWriter<W, sha2::Sha224> |
|
|
|
|
|
= ObjectWriter::new(writer, otype, size)?; |
|
|
|
|
|
|
|
|
|
|
|
copy(reader, &mut owriter)?; |
|
|
|
|
|
|
|
|
|
|
|
owriter.finish() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub struct ObjectReader<R: Read> { |
|
|
pub struct ObjectReader<R: Read> { |
|
|
|
|
|
otype: ObjectType, |
|
|
|
|
|
size: u64, |
|
|
reader: GzDecoder<R>, |
|
|
reader: GzDecoder<R>, |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl<R: Read> ObjectReader<R> { |
|
|
impl<R: Read> ObjectReader<R> { |
|
|
pub fn new(reader: R) |
|
|
pub fn new(reader: R) |
|
|
-> Result<(ObjectType, u64, ObjectReader<R>)> { |
|
|
-> Result<ObjectReader<R>> { |
|
|
|
|
|
|
|
|
let mut zreader = GzDecoder::new(reader); |
|
|
let mut zreader = GzDecoder::new(reader); |
|
|
|
|
|
|
|
|
@ -126,13 +125,19 @@ impl<R: Read> ObjectReader<R> { |
|
|
u64::from_be_bytes(size_bytes) |
|
|
u64::from_be_bytes(size_bytes) |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
Ok(( |
|
|
Ok(ObjectReader { |
|
|
otype, |
|
|
otype, |
|
|
size, |
|
|
size, |
|
|
ObjectReader { |
|
|
|
|
|
reader: zreader, |
|
|
reader: zreader, |
|
|
|
|
|
}) |
|
|
} |
|
|
} |
|
|
)) |
|
|
|
|
|
|
|
|
pub fn object_type(&self) -> ObjectType { |
|
|
|
|
|
self.otype |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub fn size(&self) -> u64 { |
|
|
|
|
|
self.size |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
pub fn close(self) -> Result<R> { |
|
|
pub fn close(self) -> Result<R> { |
|
|
@ -148,31 +153,97 @@ impl<R: Read> Read for ObjectReader<R> { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn write_object<R, W>(reader: &mut R, writer: W, |
|
|
|
|
|
otype: &ObjectType, size: u64) |
|
|
|
|
|
-> Result<(ObjectId, W)> where |
|
|
|
|
|
R: Read, |
|
|
|
|
|
W: Write { |
|
|
|
|
|
let mut owriter: ObjectWriter<W, sha2::Sha224> |
|
|
|
|
|
= ObjectWriter::new(writer, otype, size)?; |
|
|
|
|
|
|
|
|
|
|
|
copy(reader, &mut owriter)?; |
|
|
|
|
|
|
|
|
|
|
|
owriter.finish() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub trait WriteAsObject { |
|
|
|
|
|
fn write_as_object<W: Write>(&mut self, writer: W, otype: &ObjectType) -> Result<ObjectId>; |
|
|
|
|
|
|
|
|
|
|
|
fn object_id(&mut self, otype: &ObjectType) -> Result<ObjectId> { |
|
|
|
|
|
self.write_as_object(sink(), otype) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl<T: Read + Seek> WriteAsObject for T { |
|
|
|
|
|
fn write_as_object<W: Write>(&mut self, writer: W, otype: &ObjectType) -> Result<ObjectId> { |
|
|
|
|
|
let start = self.seek(SeekFrom::Current(0))?; |
|
|
|
|
|
let end = self.seek(SeekFrom::End(0))?; |
|
|
|
|
|
self.seek(SeekFrom::Start(start))?; |
|
|
|
|
|
|
|
|
|
|
|
let (oid, _) = write_object(self, writer, otype, end - start)?; |
|
|
|
|
|
Ok(oid) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)] |
|
|
#[cfg(test)] |
|
|
mod tests { |
|
|
mod tests { |
|
|
use super::*; |
|
|
use super::*; |
|
|
|
|
|
|
|
|
|
|
|
const PAYLOAD: &[u8; 12] = b"Hello World!"; |
|
|
|
|
|
const PAYLOAD_OID: &str = "c3b4032160b015b2261530532a6c49f2bdadbe0687fb1f5a6a32e083"; |
|
|
|
|
|
|
|
|
#[test] |
|
|
#[test] |
|
|
fn object_read_write() { |
|
|
fn object_read_write() -> Result<()> { |
|
|
use std::io::{Cursor, Seek, SeekFrom}; |
|
|
use std::io::{Cursor, Seek, SeekFrom}; |
|
|
|
|
|
|
|
|
let payload = b"Hello World!"; |
|
|
let mut source = Cursor::new(PAYLOAD); |
|
|
let mut source = Cursor::new(payload); |
|
|
|
|
|
let mut fake_file = Cursor::new(vec![]); |
|
|
let mut fake_file = Cursor::new(vec![]); |
|
|
let mut output = vec![]; |
|
|
let mut output = vec![]; |
|
|
|
|
|
|
|
|
let (oid, _) = write_object(&mut source, &mut fake_file, |
|
|
let (oid, _) = write_object(&mut source, &mut fake_file, |
|
|
TYPE_BLOB, payload.len() as u64).unwrap(); |
|
|
OTYPE_BLOB, PAYLOAD.len() as u64)?; |
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(oid, ObjectId::from_str(PAYLOAD_OID)?); |
|
|
|
|
|
|
|
|
|
|
|
fake_file.seek(SeekFrom::Start(0))?; |
|
|
|
|
|
let mut reader = ObjectReader::new(fake_file)?; |
|
|
|
|
|
let read_size = reader.read_to_end(&mut output)?; |
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(reader.object_type(), *OTYPE_BLOB); |
|
|
|
|
|
assert_eq!(reader.size(), PAYLOAD.len() as u64); |
|
|
|
|
|
assert_eq!(read_size, PAYLOAD.len()); |
|
|
|
|
|
assert_eq!(output, PAYLOAD); |
|
|
|
|
|
|
|
|
|
|
|
Ok(()) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
|
|
fn object_write_invalid_size() { |
|
|
|
|
|
let mut source = PAYLOAD.as_ref(); |
|
|
|
|
|
let fake_file = vec![]; |
|
|
|
|
|
|
|
|
|
|
|
let result = write_object(&mut source, fake_file, OTYPE_BLOB, 13); |
|
|
|
|
|
|
|
|
|
|
|
match result { |
|
|
|
|
|
Err(Error(ErrorKind::MismatchingObjectSize(actual, expected), _)) |
|
|
|
|
|
if actual == 12 && expected == 13 => (), |
|
|
|
|
|
Err(error) => panic!("Unexpected error: {:?}", error), |
|
|
|
|
|
Ok(_) => panic!("Unexpected success"), |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
|
|
fn write_as_object() -> Result<()> { |
|
|
|
|
|
use std::io::{Cursor, sink}; |
|
|
|
|
|
|
|
|
assert_eq!(oid, ObjectId::from_str("c3b4032160b015b2261530532a6c49f2bdadbe0687fb1f5a6a32e083").unwrap()); |
|
|
let mut source = Cursor::new(PAYLOAD); |
|
|
|
|
|
let oid = source.write_as_object(sink(), OTYPE_BLOB)?; |
|
|
|
|
|
|
|
|
fake_file.seek(SeekFrom::Start(0)).unwrap(); |
|
|
assert_eq!(oid, ObjectId::from_str(PAYLOAD_OID)?); |
|
|
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); |
|
|
Ok(()) |
|
|
assert_eq!(size, payload.len() as u64); |
|
|
|
|
|
assert_eq!(read_size, payload.len()); |
|
|
|
|
|
assert_eq!(output, payload); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|