librojo/syncback/
snapshot.rs1use indexmap::IndexMap;
2use memofs::Vfs;
3use std::path::{Path, PathBuf};
4
5use crate::{
6 snapshot::{InstanceWithMeta, RojoTree},
7 snapshot_middleware::Middleware,
8 Project,
9};
10use rbx_dom_weak::{
11 types::{Ref, Variant},
12 Instance, Ustr, UstrMap, WeakDom,
13};
14
15use super::{get_best_middleware, name_for_inst, property_filter::filter_properties};
16
17#[derive(Clone, Copy)]
18pub struct SyncbackData<'sync> {
19 pub(super) vfs: &'sync Vfs,
20 pub(super) old_tree: &'sync RojoTree,
21 pub(super) new_tree: &'sync WeakDom,
22 pub(super) project: &'sync Project,
23}
24
25pub struct SyncbackSnapshot<'sync> {
26 pub data: SyncbackData<'sync>,
27 pub old: Option<Ref>,
28 pub new: Ref,
29 pub path: PathBuf,
30 pub middleware: Option<Middleware>,
31}
32
33impl<'sync> SyncbackSnapshot<'sync> {
34 #[inline]
38 pub fn with_joined_path(&self, new_ref: Ref, old_ref: Option<Ref>) -> anyhow::Result<Self> {
39 let mut snapshot = Self {
40 data: self.data,
41 old: old_ref,
42 new: new_ref,
43 path: PathBuf::new(),
44 middleware: None,
45 };
46 let middleware = get_best_middleware(&snapshot);
47 let name = name_for_inst(middleware, snapshot.new_inst(), snapshot.old_inst())?;
48 snapshot.path = self.path.join(name.as_ref());
49
50 Ok(snapshot)
51 }
52
53 #[inline]
59 pub fn with_base_path(
60 &self,
61 base_path: &Path,
62 new_ref: Ref,
63 old_ref: Option<Ref>,
64 ) -> anyhow::Result<Self> {
65 let mut snapshot = Self {
66 data: self.data,
67 old: old_ref,
68 new: new_ref,
69 path: PathBuf::new(),
70 middleware: None,
71 };
72 let middleware = get_best_middleware(&snapshot);
73 let name = name_for_inst(middleware, snapshot.new_inst(), snapshot.old_inst())?;
74 snapshot.path = base_path.join(name.as_ref());
75
76 Ok(snapshot)
77 }
78
79 #[inline]
82 pub fn with_new_path(&self, path: PathBuf, new_ref: Ref, old_ref: Option<Ref>) -> Self {
83 Self {
84 data: self.data,
85 old: old_ref,
86 new: new_ref,
87 path,
88 middleware: None,
89 }
90 }
91
92 #[inline]
95 pub fn middleware(mut self, middleware: Middleware) -> Self {
96 self.middleware = Some(middleware);
97 self
98 }
99
100 #[inline]
108 #[must_use]
109 pub fn get_path_filtered_properties(&self, new_ref: Ref) -> Option<UstrMap<&'sync Variant>> {
110 let inst = self.get_new_instance(new_ref)?;
111
112 let properties = filter_properties(self.data.project, inst)
115 .into_iter()
116 .filter(|(name, _)| !filter_out_property(inst, name))
117 .collect();
118
119 Some(properties)
120 }
121
122 #[inline]
125 pub fn get_new_inst_path(&self, referent: Ref) -> String {
126 inst_path(self.new_tree(), referent)
127 }
128
129 #[inline]
132 pub fn get_old_inst_path(&self, referent: Ref) -> String {
133 inst_path(self.old_tree(), referent)
134 }
135
136 #[inline]
139 pub fn get_old_instance(&self, referent: Ref) -> Option<InstanceWithMeta<'sync>> {
140 self.data.old_tree.get_instance(referent)
141 }
142
143 #[inline]
146 pub fn get_new_instance(&self, referent: Ref) -> Option<&'sync Instance> {
147 self.data.new_tree.get_by_ref(referent)
148 }
149
150 #[inline]
152 pub fn old_inst(&self) -> Option<InstanceWithMeta<'sync>> {
153 self.old
154 .and_then(|old| self.data.old_tree.get_instance(old))
155 }
156
157 #[inline]
159 pub fn new_inst(&self) -> &'sync Instance {
160 self.data
161 .new_tree
162 .get_by_ref(self.new)
163 .expect("SyncbackSnapshot should not contain invalid referents")
164 }
165
166 #[inline]
168 pub fn project(&self) -> &'sync Project {
169 self.data.project
170 }
171
172 #[inline]
174 pub fn vfs(&self) -> &'sync Vfs {
175 self.data.vfs
176 }
177
178 #[inline]
180 pub fn new_tree(&self) -> &'sync WeakDom {
181 self.data.new_tree
182 }
183
184 #[inline]
186 pub fn old_tree(&self) -> &'sync WeakDom {
187 self.data.old_tree.inner()
188 }
189
190 #[inline]
192 pub fn ignore_props(&self) -> Option<&IndexMap<Ustr, Vec<Ustr>>> {
193 self.data
194 .project
195 .syncback_rules
196 .as_ref()
197 .map(|rules| &rules.ignore_properties)
198 }
199
200 #[inline]
202 pub fn ignore_tree(&self) -> Option<&[String]> {
203 self.data
204 .project
205 .syncback_rules
206 .as_ref()
207 .map(|rules| rules.ignore_trees.as_slice())
208 }
209}
210
211pub fn filter_out_property(inst: &Instance, prop_name: &str) -> bool {
212 match inst.class.as_str() {
213 "Script" | "LocalScript" | "ModuleScript" => {
214 prop_name == "Source" || prop_name == "ScriptGuid"
217 }
218 "LocalizationTable" => prop_name == "Contents",
219 "StringValue" => prop_name == "Value",
220 _ => false,
221 }
222}
223
224pub fn inst_path(dom: &WeakDom, referent: Ref) -> String {
225 let mut path = Vec::new();
226
227 let mut inst = dom.get_by_ref(referent);
228 while let Some(instance) = inst {
229 path.push(instance.name.as_str());
230 inst = dom.get_by_ref(instance.parent());
231 }
232 path.pop();
235
236 path.reverse();
237 path.join("/")
238}
239
240#[cfg(test)]
241mod test {
242 use rbx_dom_weak::{InstanceBuilder, WeakDom};
243
244 use super::inst_path as inst_path_outer;
245
246 #[test]
247 fn inst_path() {
248 let mut new_tree = WeakDom::new(InstanceBuilder::new("ROOT"));
249
250 let child_1 = new_tree.insert(new_tree.root_ref(), InstanceBuilder::new("Child1"));
251 let child_2 = new_tree.insert(child_1, InstanceBuilder::new("Child2"));
252 let child_3 = new_tree.insert(child_2, InstanceBuilder::new("Child3"));
253
254 assert_eq!(inst_path_outer(&new_tree, new_tree.root_ref()), "");
255 assert_eq!(inst_path_outer(&new_tree, child_1), "Child1");
256 assert_eq!(inst_path_outer(&new_tree, child_2), "Child1/Child2");
257 assert_eq!(inst_path_outer(&new_tree, child_3), "Child1/Child2/Child3");
258 }
259}