// 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 . 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(&self, out: &mut W) -> Result<()>; } pub trait Deserialize { fn deserialize_with_buf(stream: &mut R, buf: &mut Vec) -> Result where Self: Sized; fn deserialize(stream: &mut R) -> Result 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 { 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(&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(stream: &mut R, buf: &mut Vec) -> Result { 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(&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 { 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(stream: &mut R, buf: &mut Vec, 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, 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(stream: &mut R, buf: &mut Vec, field_name: &str, byte: u8) -> Result where R: BufRead, I: 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()); } }