winereg 0.1.0

Rust library for parsing, writing, diffing, patching, and scripting Wine/Windows registry files.
Documentation
use crate::registry_key::KeyNode;
use crate::registry_value::RegistryValue;

#[derive(Debug, Clone)]
pub enum RegistryChange {
    KeyAdded(String),
    KeyDeleted(String),
    KeyModified(String, Vec<KeyPropertyChange>),
    ValueAdded(String, String, RegistryValue),
    ValueDeleted(String, String, RegistryValue),
    ValueModified(String, String, RegistryValue, RegistryValue),
}

#[derive(Debug, Clone)]
pub enum KeyPropertyChange {
    ClassNameChange(Option<String>, Option<String>),
    SymlinkChange(bool, bool),
    VolatileChange(bool, bool),
}

#[derive(Debug, Clone)]
pub struct DiffResult {
    pub changes: Vec<RegistryChange>,
}

impl DiffResult {
    pub fn has_changes(&self) -> bool {
        !self.changes.is_empty()
    }

    pub fn added_keys(&self) -> Vec<&RegistryChange> {
        self.changes.iter().filter(|c| matches!(c, RegistryChange::KeyAdded(_))).collect()
    }
}

pub struct RegistryComparator;

impl RegistryComparator {
    pub fn compare_registries(&self, left: &KeyNode, right: &KeyNode) -> DiffResult {
        let mut changes = Vec::new();
        compare_keys(Some(left.clone()), Some(right.clone()), String::new(), &mut changes);
        DiffResult { changes }
    }
}

fn compare_keys(left: Option<KeyNode>, right: Option<KeyNode>, path: String, changes: &mut Vec<RegistryChange>) {
    match (left, right) {
        (None, Some(r)) => {
            changes.push(RegistryChange::KeyAdded(path.clone()));
            add_subtree_added(&r, &path, changes);
        }
        (Some(l), None) => {
            changes.push(RegistryChange::KeyDeleted(path.clone()));
            add_subtree_deleted(&l, &path, changes);
        }
        (Some(l), Some(r)) => {
            let l_guard = l.borrow();
            let r_guard = r.borrow();

            let mut prop_changes = Vec::new();
            if l_guard.class_name != r_guard.class_name {
                prop_changes.push(KeyPropertyChange::ClassNameChange(l_guard.class_name.clone(), r_guard.class_name.clone()));
            }
            if l_guard.is_symlink != r_guard.is_symlink {
                prop_changes.push(KeyPropertyChange::SymlinkChange(l_guard.is_symlink, r_guard.is_symlink));
            }
            if l_guard.is_volatile != r_guard.is_volatile {
                prop_changes.push(KeyPropertyChange::VolatileChange(l_guard.is_volatile, r_guard.is_volatile));
            }
            drop(l_guard);
            drop(r_guard);
            if !prop_changes.is_empty() {
                changes.push(RegistryChange::KeyModified(path.clone(), prop_changes));
            }

            compare_values(&l, &r, &path, changes);
            compare_subkeys(&l, &r, &path, changes);
        }
        (None, None) => {}
    }
}

fn compare_values(left: &KeyNode, right: &KeyNode, path: &str, changes: &mut Vec<RegistryChange>) {
    let l_vals = left.borrow().values().clone();
    let r_vals = right.borrow().values().clone();

    for (name, rv) in r_vals.iter() {
        if !l_vals.contains_key(name) {
            changes.push(RegistryChange::ValueAdded(path.to_string(), rv.name.clone(), rv.clone()));
        }
    }
    for (name, lv) in l_vals.iter() {
        if !r_vals.contains_key(name) {
            changes.push(RegistryChange::ValueDeleted(path.to_string(), lv.name.clone(), lv.clone()));
        }
    }
    for (name, lv) in l_vals.iter() {
        if let Some(rv) = r_vals.get(name) {
            if !values_equal(lv, rv) {
                changes.push(RegistryChange::ValueModified(path.to_string(), lv.name.clone(), lv.clone(), rv.clone()));
            }
        }
    }
}

fn compare_subkeys(left: &KeyNode, right: &KeyNode, path: &str, changes: &mut Vec<RegistryChange>) {
    let l_sub = left.borrow().subkeys().clone();
    let r_sub = right.borrow().subkeys().clone();
    let mut names = l_sub.keys().cloned().collect::<Vec<_>>();
    for name in r_sub.keys() {
        if !names.contains(name) {
            names.push(name.clone());
        }
    }
    names.sort();
    for name in names {
        let sub_path = if path.is_empty() { name.clone() } else { format!("{}\\{}", path, name) };
        compare_keys(l_sub.get(&name).cloned(), r_sub.get(&name).cloned(), sub_path, changes);
    }
}

fn add_subtree_added(node: &KeyNode, path: &str, changes: &mut Vec<RegistryChange>) {
    let guard = node.borrow();
    for v in guard.values().values() {
        changes.push(RegistryChange::ValueAdded(path.to_string(), v.name.clone(), v.clone()));
    }
    for (name, sub) in guard.subkeys() {
        let sub_path = if path.is_empty() { name.clone() } else { format!("{}\\{}", path, name) };
        changes.push(RegistryChange::KeyAdded(sub_path.clone()));
        add_subtree_added(sub, &sub_path, changes);
    }
}

fn add_subtree_deleted(node: &KeyNode, path: &str, changes: &mut Vec<RegistryChange>) {
    let guard = node.borrow();
    for v in guard.values().values() {
        changes.push(RegistryChange::ValueDeleted(path.to_string(), v.name.clone(), v.clone()));
    }
    for (name, sub) in guard.subkeys() {
        let sub_path = if path.is_empty() { name.clone() } else { format!("{}\\{}", path, name) };
        changes.push(RegistryChange::KeyDeleted(sub_path.clone()));
        add_subtree_deleted(sub, &sub_path, changes);
    }
}

fn values_equal(a: &RegistryValue, b: &RegistryValue) -> bool {
    a.reg_type() == b.reg_type() && a.raw_bytes() == b.raw_bytes()
}