Better error management, Utf8Paths & other stuff.
This commit is contained in:
243
libbsv/src/tree_item.rs
Normal file
243
libbsv/src/tree_item.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user