use std::fmt;
use ltk_hash::BinHash;
use crate::{
path::{PatchError, PropertyPath, ResolveError, ResolveErrorKind},
Bin, BinObject, BinOverride, PropertyPatch,
};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ApplyReport {
pub deleted: Vec<BinHash>,
pub added: Vec<BinHash>,
pub replaced: Vec<BinHash>,
pub applied: usize,
pub inserted: usize,
pub skipped: Vec<SkippedPatch>,
}
impl ApplyReport {
#[must_use]
#[inline]
pub fn is_clean(&self) -> bool {
self.skipped.is_empty()
}
}
impl fmt::Display for ApplyReport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} applied ({} inserted), {} skipped, {} deleted, {} added, {} replaced",
self.applied,
self.inserted,
self.skipped.len(),
self.deleted.len(),
self.added.len(),
self.replaced.len(),
)
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SkippedPatch {
pub index: usize,
pub object_hash: BinHash,
pub path: PropertyPath,
pub error: PatchError,
}
impl fmt::Display for SkippedPatch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"record {} ({:08x} {}): {}",
self.index, self.object_hash, self.path, self.error
)
}
}
fn missing_object(object_hash: BinHash) -> PatchError {
ResolveError::new(0, ResolveErrorKind::MissingObject(object_hash)).into()
}
impl BinOverride {
pub fn apply(self, base: &mut Bin) -> ApplyReport {
let Self {
deleted,
objects,
patches,
} = self;
let mut report = ApplyReport::default();
for object_hash in &deleted {
if base.objects.shift_remove(object_hash).is_some() {
report.deleted.push(*object_hash);
}
}
for (object_hash, object) in objects {
if deleted.contains(&object_hash) {
continue;
}
match base.objects.insert(object_hash, object) {
Some(_) => report.replaced.push(object_hash),
None => report.added.push(object_hash),
}
}
for (index, patch) in patches.into_iter().enumerate() {
let PropertyPatch {
object_hash,
path,
value,
} = patch;
let outcome = match base.objects.get_mut(&object_hash) {
Some(object) => object.patch(&path, value),
None => Err(missing_object(object_hash)),
};
match outcome {
Ok(replaced) => {
report.applied += 1;
report.inserted += usize::from(replaced.is_none());
}
Err(error) => report.skipped.push(SkippedPatch {
index,
object_hash,
path,
error,
}),
}
}
report
}
pub fn check(&self, base: &Bin) -> ApplyReport {
let mut report = ApplyReport::default();
for object_hash in &self.deleted {
if base.objects.contains_key(object_hash) {
report.deleted.push(*object_hash);
}
}
for object_hash in self.objects.keys() {
if self.deleted.contains(object_hash) {
continue;
}
match base.objects.contains_key(object_hash) {
true => report.replaced.push(*object_hash),
false => report.added.push(*object_hash),
}
}
for (index, patch) in self.patches.iter().enumerate() {
let outcome = match self.merged_object(base, patch.object_hash) {
Some(object) => object.probe(&patch.path, &patch.value),
None => Err(missing_object(patch.object_hash)),
};
match outcome {
Ok(inserted) => {
report.applied += 1;
report.inserted += usize::from(inserted);
}
Err(error) => report.skipped.push(SkippedPatch {
index,
object_hash: patch.object_hash,
path: patch.path.clone(),
error,
}),
}
}
report
}
fn merged_object<'a>(&'a self, base: &'a Bin, object_hash: BinHash) -> Option<&'a BinObject> {
if self.deleted.contains(&object_hash) {
return None;
}
self.objects
.get(&object_hash)
.or_else(|| base.objects.get(&object_hash))
}
}