Better error management, Utf8Paths & other stuff.

This commit is contained in:
2022-08-08 11:40:45 +02:00
parent b1ceaaf636
commit 5d08b1ea57
33 changed files with 1483 additions and 1216 deletions

243
libbsv/src/tree_item.rs Normal file
View File

@@ -0,0 +1,243 @@
// 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::time::{Duration, SystemTime, UNIX_EPOCH};
use std::io::{BufRead, Write};
use cas_core::{err, Error, ObjectId, ObjectType, Result};
use crate::Permissions;
pub trait Serialize {
fn serialize<W: Write>(&self, out: &mut W) -> Result<()>;
}
pub trait Deserialize {
fn deserialize_with_buf<R: BufRead>(stream: &mut R, buf: &mut Vec<u8>) -> Result<Self>
where Self: Sized;
fn deserialize<R: BufRead>(stream: &mut R) -> Result<Self>
where Self: Sized
{
let mut buf = Vec::new();
Self::deserialize_with_buf(stream, &mut buf)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TreeItem {
pub name: String,
pub otype: ObjectType,
pub size: u64,
pub created: SystemTime,
pub modified: SystemTime,
pub permissions: Permissions,
pub oid: ObjectId,
}
impl TreeItem {
pub fn from_metadata(name: &str, metadata: &std::fs::Metadata, oid: ObjectId) -> Result<Self> {
let otype = otype_from_metadata(metadata)?;
let permissions = Permissions::from_metadata(metadata)?;
Ok(Self {
name: name.to_string(),
otype,
size: metadata.len(),
created: metadata.created().unwrap_or(UNIX_EPOCH),
modified: metadata.modified().unwrap_or(UNIX_EPOCH),
permissions,
oid,
})
}
}
impl Serialize for TreeItem {
fn serialize<W: Write>(&self, out: &mut W) -> Result<()> {
// TODO: Check that name do not contain invalid characters
writeln!(out, "{}\t{}\t{}\t{}\t{}\t{}\t{}/",
self.oid,
self.otype,
self.size,
self.permissions,
self.created.duration_since(UNIX_EPOCH)
.or_else(|err| err!("failed to serialize creation time while serializing tree item: {}", err))?
.as_millis(),
self.modified.duration_since(UNIX_EPOCH)
.or_else(|err| err!("failed to serialize creation time while serializing tree item: {}", err))?
.as_millis(),
self.name,
).or_else(|err| err!("error while serializing item: {}", err))
}
}
impl Deserialize for TreeItem {
fn deserialize_with_buf<R: BufRead>(stream: &mut R, buf: &mut Vec<u8>) -> Result<Self> {
let oid: ObjectId = read_field_parse(stream, buf, "object ID", b'\t')?;
read_field(stream, buf, "object type", b'\t')?;
let otype = ObjectType::new(buf)?;
let size: u64 = read_field_parse(stream, buf, "object type", b'\t')?;
let permissions: Permissions = read_field_parse(stream, buf, "permissions", b'\t')?;
let created = UNIX_EPOCH + Duration::from_millis(
read_field_parse(stream, buf, "creation date", b'\t')?
);
let modified = UNIX_EPOCH + Duration::from_millis(
read_field_parse(stream, buf, "modification date", b'\t')?
);
let name = read_field_str(stream, buf, "name", b'/')?
.to_string();
stream.read_exact(&mut buf[..1])
.or_else(|err| err!("failed to read new line character: {}", err))?;
if buf[0] != b'\n' {
err!("expected new line character, got {:x}", buf[0])
}
else {
Ok(TreeItem {
name,
otype,
size,
created,
modified,
permissions,
oid,
})
}
}
}
impl Serialize for [TreeItem] {
fn serialize<W: Write>(&self, out: &mut W) -> Result<()> {
// assert!(self.is_sorted_by_key(|item| &item.name));
for item in self.iter() {
item.serialize(out)?
}
Ok(())
}
}
pub fn otype_from_metadata(metadata: &std::fs::Metadata) -> Result<ObjectType> {
let file_type = metadata.file_type();
if file_type.is_file() {
Ok(ObjectType::new(b"blob")?)
}
else if file_type.is_dir() {
Ok(ObjectType::new(b"tree")?)
}
else if file_type.is_symlink() {
Ok(ObjectType::new(b"link")?)
}
else {
err!("unsupported file type, must be file, dir or symlink")
}
}
fn read_field<R: BufRead>(stream: &mut R, buf: &mut Vec<u8>, field_name: &str, byte: u8) -> Result<()> {
buf.clear();
stream.read_until(byte, buf)
.or_else(|err| err!("failed to read TreeItem {}: {}", field_name, err))?;
buf.pop();
Ok(())
}
fn read_field_str<'a, R: BufRead>(stream: &mut R, buf: &'a mut Vec<u8>, field_name: &str, byte: u8) -> Result<&'a str> {
read_field(stream, buf, field_name, byte)?;
std::str::from_utf8(buf)
.or_else(|err| err!("TreeItem {} is not valid utf-8: {}", field_name, err))
}
fn read_field_parse<R, I>(stream: &mut R, buf: &mut Vec<u8>, field_name: &str, byte: u8) -> Result<I>
where
R: BufRead,
I: std::str::FromStr,
<I as std::str::FromStr>::Err: std::fmt::Display
{
let int_str = read_field_str(stream, buf, field_name, byte)?;
I::from_str(int_str)
.or_else(|err| err!("failed to parse TreeItem {}: {}", field_name, err))
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use std::string::ToString;
use super::*;
#[test]
fn test_serialize_tree_item() {
let item = TreeItem {
name: "Test $¢ह€한".to_string(),
otype: ObjectType::new(b"test").unwrap(),
size: 42,
created: UNIX_EPOCH + Duration::from_secs(1234),
modified: UNIX_EPOCH + Duration::from_secs(5678),
permissions: Permissions { read: true, write: false, execute: true },
oid: ObjectId::from_str("0123456789abcdef").unwrap(),
};
let expected = "0123456789abcdef\ttest\t42\tr-x\t1234000\t5678000\tTest $¢ह€한/\n".as_bytes();
let mut result = Vec::new();
item.serialize(&mut result).unwrap();
assert_eq!(result, expected);
}
#[test]
fn test_deserialize_tree_item() {
use std::io::Cursor;
let item_bytes = "0123456789abcdef\ttest\t42\tr-x\t1234000\t5678000\tTest $¢ह€한/\n".as_bytes();
let mut item_cursor = Cursor::new(item_bytes);
let expected = TreeItem {
name: "Test $¢ह€한".to_string(),
otype: ObjectType::new(b"test").unwrap(),
size: 42,
created: UNIX_EPOCH + Duration::from_secs(1234),
modified: UNIX_EPOCH + Duration::from_secs(5678),
permissions: Permissions { read: true, write: false, execute: true },
oid: ObjectId::from_str("0123456789abcdef").unwrap(),
};
let item = TreeItem::deserialize(&mut item_cursor).unwrap();
assert_eq!(item, expected);
assert!(TreeItem::deserialize(&mut Cursor::new(
"0123456789abcdef\ttest\t42\tr-x\t1234000\t5678000\tTest $¢ह€한/".as_bytes()
)).is_err());
assert!(TreeItem::deserialize(&mut Cursor::new(
"0123456789abcdef\ttest\t42\tr-x\t1234000\t5678000\tTest $¢ह€한/bar/\n".as_bytes()
)).is_err());
assert!(TreeItem::deserialize(&mut Cursor::new(
"0123456789abcdef\ttest\t42\tr-x\t1234000\t5678000\tTest $¢ह€한\t\n".as_bytes()
)).is_err());
assert!(TreeItem::deserialize(&mut Cursor::new(
"0123456789abcdef\ttest\t42\tr-x\t5678000\tTest $¢ह€한\t\n".as_bytes()
)).is_err());
assert!(TreeItem::deserialize(&mut Cursor::new(
"0123456789abcdef\ttest\tab\tr-x\t1234000\t5678000\tTest $¢ह€한\t\n".as_bytes()
)).is_err());
}
}