use std::fmt;
use serde::Serialize;
use time::OffsetDateTime;
use crate::{Apath, EntryTrait, Kind, Owner, Result, UnixMode};
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
pub struct EntryChange {
pub apath: Apath,
#[serde(flatten)]
pub change: Change<EntryMetadata>,
}
impl EntryChange {
pub(crate) fn diff_metadata<AE: EntryTrait, BE: EntryTrait>(a: &AE, b: &BE) -> Self {
debug_assert_eq!(a.apath(), b.apath());
let ak = a.kind();
if ak != b.kind()
|| a.owner() != b.owner()
|| a.unix_mode() != b.unix_mode()
|| (ak == Kind::File && (a.size() != b.size() || a.mtime() != b.mtime()))
|| (ak == Kind::Symlink && (a.symlink_target() != b.symlink_target()))
{
EntryChange::changed(a, b)
} else {
EntryChange::unchanged(a)
}
}
pub(crate) fn added(entry: &dyn EntryTrait) -> Self {
EntryChange {
apath: entry.apath().clone(),
change: Change::Added {
added: EntryMetadata::from(entry),
},
}
}
#[allow(unused)] pub(crate) fn deleted(entry: &dyn EntryTrait) -> Self {
EntryChange {
apath: entry.apath().clone(),
change: Change::Deleted {
deleted: EntryMetadata::from(entry),
},
}
}
pub(crate) fn unchanged(entry: &dyn EntryTrait) -> Self {
EntryChange {
apath: entry.apath().clone(),
change: Change::Unchanged {
unchanged: EntryMetadata::from(entry),
},
}
}
pub(crate) fn changed(old: &dyn EntryTrait, new: &dyn EntryTrait) -> Self {
debug_assert_eq!(old.apath(), new.apath());
EntryChange {
apath: old.apath().clone(),
change: Change::Changed {
old: EntryMetadata::from(old),
new: EntryMetadata::from(new),
},
}
}
}
impl fmt::Display for EntryChange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}", self.change.sigil(), self.apath)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
#[serde(tag = "change")]
pub enum Change<E> {
Unchanged { unchanged: E },
Added { added: E },
Deleted { deleted: E },
Changed { old: E, new: E },
}
impl<E> Change<E> {
pub fn is_unchanged(&self) -> bool {
matches!(self, Change::Unchanged { .. })
}
pub fn is_changed(&self) -> bool {
matches!(self, Change::Changed { .. })
}
pub fn primary_metadata(&self) -> &E {
match self {
Change::Unchanged { unchanged } => unchanged,
Change::Added { added } => added,
Change::Deleted { deleted } => deleted,
Change::Changed { new, .. } => new,
}
}
pub fn sigil(&self) -> char {
match self {
Change::Unchanged { .. } => '.',
Change::Added { .. } => '+',
Change::Deleted { .. } => '-',
Change::Changed { .. } => '*',
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
pub struct EntryMetadata {
#[serde(flatten)]
pub kind: KindMetadata,
pub mtime: OffsetDateTime,
#[serde(flatten)]
pub owner: Owner,
pub unix_mode: UnixMode,
}
impl From<&dyn EntryTrait> for EntryMetadata {
fn from(entry: &dyn EntryTrait) -> Self {
EntryMetadata {
kind: KindMetadata::from(entry),
mtime: entry.mtime(),
owner: entry.owner().clone(),
unix_mode: entry.unix_mode(),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
#[serde(tag = "kind")]
pub enum KindMetadata {
File { size: u64 },
Dir,
Symlink { target: String },
}
impl From<&dyn EntryTrait> for KindMetadata {
fn from(entry: &dyn EntryTrait) -> Self {
match entry.kind() {
Kind::File => KindMetadata::File {
size: entry.size().unwrap(),
},
Kind::Dir => KindMetadata::Dir,
Kind::Symlink => KindMetadata::Symlink {
target: entry.symlink_target().unwrap().to_owned(),
},
Kind::Unknown => panic!("unexpected Kind::Unknown on {:?}", entry.apath()),
}
}
}
pub type ChangeCallback<'cb> = Box<dyn Fn(&EntryChange) -> Result<()> + 'cb>;