244 lines
8.1 KiB
Rust
244 lines
8.1 KiB
Rust
// 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());
|
|
}
|
|
}
|