From e6121a651c8b976f0d97ad86cf7e5fc09837173e Mon Sep 17 00:00:00 2001 From: Joe Date: Thu, 5 Feb 2026 20:50:33 -0600 Subject: [PATCH] Refactor file handling in JobThreadContext and ApiService for improved metadata management - Updated the logic for deriving file names in JobThreadContext to utilize encoded base names from filesystem paths, enhancing consistency and preserving character encoding. - Enhanced ApiService to differentiate between model files and other file types, ensuring correct handling of in-place updates and adjacent meta file creation for unsupported formats. - Improved logging for property persistence operations, providing clearer insights during file updates and enhancing debugging capabilities. --- src/change_processor.rs | 25 +++++++++++++--- src/web/api.rs | 65 +++++++++++++++++++++++++++++++---------- 2 files changed, 70 insertions(+), 20 deletions(-) diff --git a/src/change_processor.rs b/src/change_processor.rs index d2426e7a..c704c2ec 100644 --- a/src/change_processor.rs +++ b/src/change_processor.rs @@ -674,7 +674,6 @@ impl JobThreadContext { }; if let Some((actual_file, file_parent)) = init_result { - let name = instance.name(); let new_suffix = match new_class.as_str() { "ModuleScript" => "", "Script" => ".server", @@ -693,10 +692,28 @@ impl JobThreadContext { } else { format!("init{}.luau", new_suffix) } - } else if new_suffix.is_empty() { - format!("{}.luau", name) } else { - format!("{}{}.luau", name, new_suffix) + // Derive the encoded base name from the + // filesystem path, not instance.name() + // (which is decoded). This preserves + // encoding like What%QUESTION% on disk. + let stem = actual_file + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or(""); + let known_suffixes = [ + ".server", ".client", ".plugin", + ".local", ".legacy", + ]; + let base = known_suffixes + .iter() + .find_map(|s| stem.strip_suffix(s)) + .unwrap_or(stem); + if new_suffix.is_empty() { + format!("{}.luau", base) + } else { + format!("{}{}.luau", base, new_suffix) + } }; let new_path = file_parent.join(&new_file_name); diff --git a/src/web/api.rs b/src/web/api.rs index ea0fb886..41007fd1 100644 --- a/src/web/api.rs +++ b/src/web/api.rs @@ -2218,23 +2218,56 @@ impl ApiService { meta_path.display() ); } else { - // Non-script standalone file (e.g., .model.json5): update in place - let meta = self.merge_or_build_meta( - inst_path, - Some(class_name.as_str()), - properties, - attributes, - )?; - let content = crate::json::to_vec_pretty_sorted(&meta) - .context("Failed to serialize model file")?; - self.suppress_path(inst_path); - fs::write(inst_path, &content) - .with_context(|| format!("Failed to write {}", inst_path.display()))?; + // Non-script standalone file. Only .model.json5/.model.json support + // in-place JSON property updates. Other file types (.txt, .csv, .toml, + // .yaml, etc.) require an adjacent .meta.json5 file — writing JSON + // directly to them would corrupt their content. + let file_name = inst_path + .file_name() + .and_then(|f| f.to_str()) + .unwrap_or(""); + let is_model_file = file_name.ends_with(".model.json5") + || file_name.ends_with(".model.json"); - log::info!( - "Syncback: Persisted non-Source properties to {}", - inst_path.display() - ); + if is_model_file { + let meta = self.merge_or_build_meta( + inst_path, + Some(class_name.as_str()), + properties, + attributes, + )?; + let content = crate::json::to_vec_pretty_sorted(&meta) + .context("Failed to serialize model file")?; + self.suppress_path(inst_path); + fs::write(inst_path, &content) + .with_context(|| format!("Failed to write {}", inst_path.display()))?; + + log::info!( + "Syncback: Persisted non-Source properties to {}", + inst_path.display() + ); + } else { + // For .txt, .csv, .toml, .yaml, etc. — use adjacent meta file + let parent_dir = inst_path.parent().context("No parent directory")?; + let file_stem = inst_path + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or(""); + let meta_path = parent_dir.join(format!("{}.meta.json5", file_stem)); + + let meta = + self.merge_or_build_meta(&meta_path, None, properties, attributes)?; + let content = crate::json::to_vec_pretty_sorted(&meta) + .context("Failed to serialize meta.json5")?; + self.suppress_path(&meta_path); + fs::write(&meta_path, &content) + .with_context(|| format!("Failed to write {}", meta_path.display()))?; + + log::info!( + "Syncback: Persisted non-Source properties to {}", + meta_path.display() + ); + } } Ok(())