haggis-rs/src/listing.rs

429 lines
14 KiB
Rust
Raw Normal View History

#[cfg(feature = "color")]
use {
std::io::Write,
termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor},
};
use {
2024-01-01 18:30:24 -05:00
crate::{filetype::Flag, Error, FileType, Node, Special},
2023-12-30 19:01:37 -05:00
chrono::NaiveDateTime,
2023-12-29 00:31:49 -05:00
std::{
cmp::Ordering,
fmt,
io::{Read, Seek, SeekFrom},
},
};
2023-12-29 23:52:05 -05:00
#[derive(Debug, PartialEq, Eq)]
pub enum Kind {
Normal(u64),
HardLink(String),
SoftLink(String),
Directory,
Character(Special),
Block(Special),
Fifo,
Eof,
}
2024-01-01 18:30:24 -05:00
impl From<FileType> for Kind {
fn from(value: FileType) -> Self {
match value {
FileType::Normal(f) => Self::Normal(f.len),
FileType::HardLink(tgt) => Self::HardLink(tgt),
FileType::SoftLink(tgt) => Self::SoftLink(tgt),
FileType::Directory => Self::Directory,
FileType::Character(c) => Self::Character(c),
FileType::Block(b) => Self::Block(b),
FileType::Fifo => Self::Fifo,
FileType::Eof => Self::Eof,
}
}
}
impl Kind {
#[allow(clippy::cast_possible_wrap)]
fn read<R: Read + Seek>(reader: &mut R, flag: Flag) -> Result<Self, Error> {
match flag {
Flag::Normal => {
let mut len = [0; 8];
reader.read_exact(&mut len)?;
let len = u64::from_le_bytes(len);
let mut buf = [0; 1];
reader.read_exact(&mut buf)?;
match buf[0] {
0 => {
2023-12-29 00:31:49 -05:00
let _bytes = reader.seek(SeekFrom::Current(len as i64 + 16))?;
}
1 => {
2023-12-29 00:31:49 -05:00
let _bytes = reader.seek(SeekFrom::Current(len as i64 + 20))?;
}
2 => {
2023-12-29 00:31:49 -05:00
let _bytes = reader.seek(SeekFrom::Current(len as i64 + 32))?;
}
_ => {
let _bytes = reader.seek(SeekFrom::Current(len as i64))?;
}
}
Ok(Self::Normal(len))
}
Flag::HardLink => {
let mut len = [0; 2];
reader.read_exact(&mut len)?;
let len = u16::from_le_bytes(len);
let mut buf = Vec::with_capacity(len.into());
let mut handle = reader.take(len.into());
handle.read_to_end(&mut buf)?;
let s = String::from_utf8(buf)?;
Ok(Self::HardLink(s))
}
Flag::SoftLink => {
let mut len = [0; 2];
reader.read_exact(&mut len)?;
let len = u16::from_le_bytes(len);
let mut buf = Vec::with_capacity(len.into());
let mut handle = reader.take(len.into());
handle.read_to_end(&mut buf)?;
let s = String::from_utf8(buf)?;
Ok(Self::SoftLink(s))
}
Flag::Directory => Ok(Self::Directory),
Flag::Character => {
let sp = Special::read(reader)?;
Ok(Self::Character(sp))
}
Flag::Block => {
let sp = Special::read(reader)?;
Ok(Self::Block(sp))
}
Flag::Fifo => Ok(Self::Fifo),
Flag::Eof => Ok(Self::Eof),
}
}
}
2023-12-29 23:52:05 -05:00
#[derive(Debug, PartialEq, Eq)]
pub struct Listing {
pub name: String,
pub uid: u32,
pub gid: u32,
pub mtime: u64,
pub mode: u16,
pub kind: Kind,
}
2024-01-01 18:30:24 -05:00
impl From<Node> for Listing {
fn from(value: Node) -> Self {
Self {
name: value.name,
uid: value.uid,
gid: value.gid,
mtime: value.mtime,
mode: value.mode,
kind: value.filetype.into(),
}
}
}
impl PartialOrd for Listing {
2023-12-29 00:31:49 -05:00
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
2023-12-29 23:52:05 -05:00
Some(self.cmp(other))
}
}
impl Ord for Listing {
fn cmp(&self, other: &Self) -> Ordering {
2023-12-29 00:31:49 -05:00
match (&self.kind, &other.kind) {
2023-12-29 23:52:05 -05:00
(Kind::Directory, Kind::Directory) => self.name.cmp(&other.name),
(Kind::Directory, _) => Ordering::Less,
(_, Kind::Directory) => Ordering::Greater,
_ => self.name.cmp(&other.name),
}
}
}
impl fmt::Display for Listing {
#[allow(clippy::cast_possible_wrap)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{}{}{}{}{}{}{}{}{} {:>4}:{:<4} {:>10} ",
match self.kind {
Kind::Normal(_) => "-",
Kind::HardLink(_) => "L",
Kind::SoftLink(_) => "l",
Kind::Directory => "d",
Kind::Character(_) => "c",
Kind::Block(_) => "b",
Kind::Fifo => "p",
Kind::Eof => return Ok(()),
},
match self.mode {
m if m & 0o400 != 0 => "r",
_ => "-",
},
match self.mode {
m if m & 0o200 != 0 => "w",
_ => "-",
},
match self.mode {
2023-12-30 19:01:37 -05:00
m if m & 0o4000 != 0 => "S",
m if m & 0o100 != 0 => "x",
_ => "-",
},
match self.mode {
m if m & 0o40 != 0 => "r",
_ => "-",
},
match self.mode {
m if m & 0o20 != 0 => "w",
_ => "-",
},
match self.mode {
2023-12-30 19:01:37 -05:00
m if m & 0o2000 != 0 => "S",
m if m & 0o10 != 0 => "x",
_ => "-",
},
match self.mode {
m if m & 0o4 != 0 => "r",
_ => "-",
},
match self.mode {
m if m & 0o2 != 0 => "w",
_ => "-",
},
match self.mode {
2023-12-30 19:01:37 -05:00
m if m & 0o1000 != 0 => "t",
m if m & 0o1 != 0 => "x",
_ => "-",
},
self.uid,
self.gid,
match self.kind {
Kind::Normal(s) => s,
_ => 0,
},
)?;
match NaiveDateTime::from_timestamp_opt(self.mtime as i64, 0) {
Some(dt) => write!(f, "{dt} ")?,
_ => write!(f, "{:>19} ", self.mtime)?,
}
match self.kind {
Kind::Directory | Kind::Fifo | Kind::Normal(_) => write!(f, "{}", self.name),
Kind::HardLink(ref tgt) => write!(f, "{}=>{}", self.name, tgt),
Kind::SoftLink(ref tgt) => write!(f, "{}->{}", self.name, tgt),
Kind::Character(ref sp) | Kind::Block(ref sp) => {
write!(f, "{} {},{}", self.name, sp.major, sp.minor)
}
Kind::Eof => unreachable!(),
}
}
}
impl Listing {
/// Reads all metadata from a haggis Node without reading the file's actual
/// data.
/// # Errors
/// Can return an error if IO fails
#[allow(clippy::similar_names)]
pub fn read<R: Read + Seek>(reader: &mut R) -> Result<Self, Error> {
let mut len = [0; 2];
reader.read_exact(&mut len)?;
let len = u16::from_le_bytes(len);
if len == 0 {
return Ok(Self {
name: String::new(),
mode: 0,
uid: 0,
gid: 0,
mtime: 0,
kind: Kind::Eof,
});
}
let mut name = Vec::with_capacity(len.into());
let mut handle = reader.take(len.into());
handle.read_to_end(&mut name)?;
let mut buf = [0; 18];
reader.read_exact(&mut buf)?;
let uid: [u8; 4] = buf[0..4].try_into()?;
let gid: [u8; 4] = buf[4..8].try_into()?;
let mtime: [u8; 8] = buf[8..16].try_into()?;
let raw_mode: [u8; 2] = buf[16..18].try_into()?;
let raw_mode = u16::from_le_bytes(raw_mode);
let (flag, mode) = Flag::extract_from_raw(raw_mode)?;
let kind = Kind::read(reader, flag)?;
Ok(Self {
name: String::from_utf8(name)?,
uid: u32::from_le_bytes(uid),
gid: u32::from_le_bytes(gid),
mtime: u64::from_le_bytes(mtime),
mode,
kind,
})
}
#[allow(clippy::cast_possible_wrap)]
#[cfg(feature = "color")]
pub fn print_color(&self) -> Result<(), Error> {
print!(
"{}{}{}{}{}{}{}{}{}{} {:>4}:{:<4} {:>10} ",
match self.kind {
Kind::Normal(_) => "-",
Kind::HardLink(_) => "L",
Kind::SoftLink(_) => "l",
Kind::Directory => "d",
Kind::Character(_) => "c",
Kind::Block(_) => "b",
Kind::Fifo => "p",
Kind::Eof => return Ok(()),
},
match self.mode {
m if m & 0o400 != 0 => "r",
_ => "-",
},
match self.mode {
m if m & 0o200 != 0 => "w",
_ => "-",
},
match self.mode {
m if m & 0o4000 != 0 => "S",
m if m & 0o100 != 0 => "x",
_ => "-",
},
match self.mode {
m if m & 0o40 != 0 => "r",
_ => "-",
},
match self.mode {
m if m & 0o20 != 0 => "w",
_ => "-",
},
match self.mode {
m if m & 0o2000 != 0 => "S",
m if m & 0o10 != 0 => "x",
_ => "-",
},
match self.mode {
m if m & 0o4 != 0 => "r",
_ => "-",
},
match self.mode {
m if m & 0o2 != 0 => "w",
_ => "-",
},
match self.mode {
m if m & 0o1000 != 0 => "t",
m if m & 0o1 != 0 => "x",
_ => "-",
},
self.uid,
self.gid,
match self.kind {
Kind::Normal(s) => s,
_ => 0,
},
);
match NaiveDateTime::from_timestamp_opt(self.mtime as i64, 0) {
Some(dt) => print!("{dt} "),
_ => print!("{:>19} ", self.mtime),
}
let mut stdout = StandardStream::stdout(ColorChoice::Auto);
match self.kind {
Kind::Normal(_) => println!("{}", self.name),
Kind::Directory => {
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Blue)))?;
writeln!(&mut stdout, "{}", self.name)?;
stdout.reset()?;
}
Kind::Fifo => {
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?;
writeln!(&mut stdout, "{}", self.name)?;
stdout.reset()?;
}
Kind::HardLink(ref tgt) => {
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)))?;
write!(&mut stdout, "{}", self.name)?;
stdout.reset()?;
println!(" => {tgt}");
}
Kind::SoftLink(ref tgt) => {
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)))?;
write!(&mut stdout, "{}", self.name)?;
stdout.reset()?;
println!(" -> {tgt}");
}
Kind::Character(ref sp) | Kind::Block(ref sp) => {
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?;
write!(&mut stdout, "{}", self.name)?;
stdout.reset()?;
println!(" {},{}", sp.major, sp.minor);
}
Kind::Eof => unreachable!(),
}
Ok(())
}
#[cfg(feature = "color")]
pub fn print_color_simple(&self) -> Result<(), Error> {
let mut stdout = StandardStream::stdout(ColorChoice::Auto);
match self.kind {
Kind::Normal(_) => println!("{}", self.name),
Kind::Directory => {
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Blue)))?;
writeln!(&mut stdout, "{}", self.name)?;
}
Kind::Fifo => {
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?;
writeln!(&mut stdout, "{}", self.name)?;
}
Kind::HardLink(_) | Kind::SoftLink(_) => {
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)))?;
write!(&mut stdout, "{}", self.name)?;
}
Kind::Character(_) | Kind::Block(_) => {
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?;
write!(&mut stdout, "{}", self.name)?;
}
Kind::Eof => unreachable!(),
}
stdout.reset()?;
Ok(())
}
}
2023-12-29 00:31:49 -05:00
#[cfg(test)]
mod tests {
use super::*;
use crate::{Algorithm, Node};
use std::{
collections::HashMap,
fs,
io::{BufReader, BufWriter, Write},
sync::Mutex,
};
#[test]
2023-12-30 19:01:37 -05:00
fn listing() {
2023-12-29 00:31:49 -05:00
let links = Mutex::new(HashMap::new());
{
let mut node = Node::from_path("test", Algorithm::Skip, &links).unwrap();
let fd = fs::File::create("test/ord_test.hag").unwrap();
let mut writer = BufWriter::new(fd);
node.write(&mut writer).unwrap();
node = Node::from_path("Cargo.toml", Algorithm::Skip, &links).unwrap();
node.write(&mut writer).unwrap();
node = Node::from_path("Cargo.lock", Algorithm::Skip, &links).unwrap();
node.write(&mut writer).unwrap();
writer.flush().unwrap();
}
let fd = fs::File::open("test/ord_test.hag").unwrap();
let mut reader = BufReader::new(fd);
let test_listing = Listing::read(&mut reader).unwrap();
let cargo_toml_listing = Listing::read(&mut reader).unwrap();
assert!(test_listing < cargo_toml_listing);
let cargo_lock_listing = Listing::read(&mut reader).unwrap();
assert!(test_listing < cargo_lock_listing);
assert!(cargo_lock_listing < cargo_toml_listing);
}
}