use indexmap::IndexMap;
use memofs::Vfs;
use std::path::{Path, PathBuf};
use crate::{
snapshot::{InstanceWithMeta, RojoTree},
snapshot_middleware::Middleware,
Project,
};
use rbx_dom_weak::{
types::{Ref, Variant},
Instance, Ustr, UstrMap, WeakDom,
};
use super::{get_best_middleware, name_for_inst, property_filter::filter_properties};
#[derive(Clone, Copy)]
pub struct SyncbackData<'sync> {
pub(super) vfs: &'sync Vfs,
pub(super) old_tree: &'sync RojoTree,
pub(super) new_tree: &'sync WeakDom,
pub(super) project: &'sync Project,
}
pub struct SyncbackSnapshot<'sync> {
pub data: SyncbackData<'sync>,
pub old: Option<Ref>,
pub new: Ref,
pub path: PathBuf,
pub middleware: Option<Middleware>,
}
impl<'sync> SyncbackSnapshot<'sync> {
#[inline]
pub fn with_joined_path(&self, new_ref: Ref, old_ref: Option<Ref>) -> anyhow::Result<Self> {
let mut snapshot = Self {
data: self.data,
old: old_ref,
new: new_ref,
path: PathBuf::new(),
middleware: None,
};
let middleware = get_best_middleware(&snapshot);
let name = name_for_inst(middleware, snapshot.new_inst(), snapshot.old_inst())?;
snapshot.path = self.path.join(name.as_ref());
Ok(snapshot)
}
#[inline]
pub fn with_base_path(
&self,
base_path: &Path,
new_ref: Ref,
old_ref: Option<Ref>,
) -> anyhow::Result<Self> {
let mut snapshot = Self {
data: self.data,
old: old_ref,
new: new_ref,
path: PathBuf::new(),
middleware: None,
};
let middleware = get_best_middleware(&snapshot);
let name = name_for_inst(middleware, snapshot.new_inst(), snapshot.old_inst())?;
snapshot.path = base_path.join(name.as_ref());
Ok(snapshot)
}
#[inline]
pub fn with_new_path(&self, path: PathBuf, new_ref: Ref, old_ref: Option<Ref>) -> Self {
Self {
data: self.data,
old: old_ref,
new: new_ref,
path,
middleware: None,
}
}
#[inline]
pub fn middleware(mut self, middleware: Middleware) -> Self {
self.middleware = Some(middleware);
self
}
#[inline]
#[must_use]
pub fn get_path_filtered_properties(&self, new_ref: Ref) -> Option<UstrMap<&'sync Variant>> {
let inst = self.get_new_instance(new_ref)?;
let properties = filter_properties(self.data.project, inst)
.into_iter()
.filter(|(name, _)| !filter_out_property(inst, name))
.collect();
Some(properties)
}
#[inline]
pub fn get_new_inst_path(&self, referent: Ref) -> String {
inst_path(self.new_tree(), referent)
}
#[inline]
pub fn get_old_inst_path(&self, referent: Ref) -> String {
inst_path(self.old_tree(), referent)
}
#[inline]
pub fn get_old_instance(&self, referent: Ref) -> Option<InstanceWithMeta<'sync>> {
self.data.old_tree.get_instance(referent)
}
#[inline]
pub fn get_new_instance(&self, referent: Ref) -> Option<&'sync Instance> {
self.data.new_tree.get_by_ref(referent)
}
#[inline]
pub fn old_inst(&self) -> Option<InstanceWithMeta<'sync>> {
self.old
.and_then(|old| self.data.old_tree.get_instance(old))
}
#[inline]
pub fn new_inst(&self) -> &'sync Instance {
self.data
.new_tree
.get_by_ref(self.new)
.expect("SyncbackSnapshot should not contain invalid referents")
}
#[inline]
pub fn project(&self) -> &'sync Project {
self.data.project
}
#[inline]
pub fn vfs(&self) -> &'sync Vfs {
self.data.vfs
}
#[inline]
pub fn new_tree(&self) -> &'sync WeakDom {
self.data.new_tree
}
#[inline]
pub fn old_tree(&self) -> &'sync WeakDom {
self.data.old_tree.inner()
}
#[inline]
pub fn ignore_props(&self) -> Option<&IndexMap<Ustr, Vec<Ustr>>> {
self.data
.project
.syncback_rules
.as_ref()
.map(|rules| &rules.ignore_properties)
}
#[inline]
pub fn ignore_tree(&self) -> Option<&[String]> {
self.data
.project
.syncback_rules
.as_ref()
.map(|rules| rules.ignore_trees.as_slice())
}
}
pub fn filter_out_property(inst: &Instance, prop_name: &str) -> bool {
match inst.class.as_str() {
"Script" | "LocalScript" | "ModuleScript" => {
prop_name == "Source" || prop_name == "ScriptGuid"
}
"LocalizationTable" => prop_name == "Contents",
"StringValue" => prop_name == "Value",
_ => false,
}
}
pub fn inst_path(dom: &WeakDom, referent: Ref) -> String {
let mut path = Vec::new();
let mut inst = dom.get_by_ref(referent);
while let Some(instance) = inst {
path.push(instance.name.as_str());
inst = dom.get_by_ref(instance.parent());
}
path.pop();
path.reverse();
path.join("/")
}
#[cfg(test)]
mod test {
use rbx_dom_weak::{InstanceBuilder, WeakDom};
use super::inst_path as inst_path_outer;
#[test]
fn inst_path() {
let mut new_tree = WeakDom::new(InstanceBuilder::new("ROOT"));
let child_1 = new_tree.insert(new_tree.root_ref(), InstanceBuilder::new("Child1"));
let child_2 = new_tree.insert(child_1, InstanceBuilder::new("Child2"));
let child_3 = new_tree.insert(child_2, InstanceBuilder::new("Child3"));
assert_eq!(inst_path_outer(&new_tree, new_tree.root_ref()), "");
assert_eq!(inst_path_outer(&new_tree, child_1), "Child1");
assert_eq!(inst_path_outer(&new_tree, child_2), "Child1/Child2");
assert_eq!(inst_path_outer(&new_tree, child_3), "Child1/Child2/Child3");
}
}