use crate::store::ModelStore;
use brep_render::engine_state::{EngineState, NoticeSeverity};
pub fn refresh_library_entry(
assembly: &mut serde_json::Value,
part_name: &str,
document_json: &str,
) -> Result<Vec<String>, String> {
let document: serde_json::Value = serde_json::from_str(document_json)
.map_err(|error| format!("part document unreadable: {error}"))?;
refresh_library_entry_value(assembly, part_name, document)
}
pub fn refresh_library_entry_value(
assembly: &mut serde_json::Value,
part_name: &str,
document: serde_json::Value,
) -> Result<Vec<String>, String> {
if !document.is_object() {
return Err("part document unreadable: not a JSON object".to_string());
}
let signature = format!("{:016x}", brep_render::brep_kernel::stable_json_hash(&document));
let sheets = ["diagram", "pcb"].iter().any(|block| assembly.get(*block).is_some_and(|v| !v.is_null()));
if !sheets {
let entry = match assembly["partsLibrary"].get_mut(part_name) {
Some(entry) if entry.is_object() => entry,
_ => return Err(format!("no parts-library entry '{part_name}'")),
};
let old = std::mem::take(&mut entry["document"]);
let rebuild = !brep_render::brep_kernel::same_build(&old, &document);
brep_render::brep_kernel::follow_saved_rows_known(entry, &old, &document, !rebuild);
entry["document"] = document;
entry["sourceSignature"] = serde_json::Value::String(signature);
if rebuild {
entry["snapshot"] = serde_json::Value::String(String::new());
}
return Ok(Vec::new());
}
let mut draft = assembly.clone();
let old = draft["partsLibrary"][part_name]["document"].clone();
match draft["partsLibrary"].get_mut(part_name) {
Some(entry) if entry.is_object() => {
let rebuild = !brep_render::brep_kernel::same_build(&old, &document);
brep_render::brep_kernel::follow_saved_rows_known(entry, &old, &document, !rebuild);
entry["document"] = document;
entry["sourceSignature"] = serde_json::Value::String(signature);
if rebuild {
entry["snapshot"] = serde_json::Value::String(String::new());
}
}
_ => return Err(format!("no parts-library entry '{part_name}'")),
}
let mut notes = super::ecad_parts::refresh_snapshots(&mut draft, assembly, part_name)?;
if let Some(value) = draft.get("diagram").filter(|v| !v.is_null()) {
let sheet = brep_ecad_core::Document::from_value(value.clone())?;
let endpoints = super::ecad_parts::declared_endpoints(&draft);
notes.extend(super::ecad_parts::follow_sheet(&mut draft, "diagram", &sheet, &endpoints)?);
}
*assembly = draft;
Ok(notes)
}
pub(crate) fn write_through(
state: &mut EngineState,
store: &dyn ModelStore,
part_name: &str,
target: Option<&(String, String)>,
edited: &str,
) -> WriteOutcome {
let Some((source_key, inserted_signature)) = target else {
return WriteOutcome::Unchanged;
};
let current = crate::store::read_now(store, source_key);
let crate::store::ReadNow::Ready(current) = current else {
if current == crate::store::ReadNow::Loading {
state.push_notice_as(NoticeSeverity::Info, format!(
"Source '{source_key}' is still loading — '{part_name}' updated in this document only; \
edit it again once it has loaded to write the part"
));
return WriteOutcome::Refused("still loading".into());
}
state.push_notice_as(NoticeSeverity::Warning, format!(
"Source '{source_key}' is no longer in storage — \
'{part_name}' updated in this document only, the file was NOT written"
));
return WriteOutcome::Refused("no longer in storage".into());
};
if let Some(source) = serde_json::from_str::<serde_json::Value>(¤t)
.ok()
.as_ref()
.and_then(crate::family_table::family_source)
{
state.push_notice_as(NoticeSeverity::Warning, format!(
"'{source_key}' is member '{}' of the family '{}' — NOT overwritten; \
'{part_name}' updated in this document only (change the family, or save the part as a new part)",
source.part_number, source.family
));
return WriteOutcome::Refused(format!("member of the family '{}'", source.family));
}
let on_disk = document_signature(¤t);
let ours = store.write_failed(source_key);
if on_disk == document_signature(edited) && !ours {
return WriteOutcome::Unchanged;
}
if on_disk != *inserted_signature && !ours {
state.push_notice_as(NoticeSeverity::Warning, format!(
"Source '{source_key}' changed since this document last synced '{part_name}' — \
NOT overwritten; your edit is held here (Update Components takes the file's version instead)"
));
return WriteOutcome::Refused("changed on disk since this assembly loaded it".into());
}
let written = match store.failed_write_base(source_key).filter(|base| ours && !base.is_empty()) {
Some(base) => store.write_if(source_key, edited, &base),
None => store.write(source_key, edited),
};
match written {
Ok(()) => {
state.push_notice_as(NoticeSeverity::Info, crate::document::owned_save::saved_notice(part_name, source_key));
WriteOutcome::Written
}
Err(error) => {
state.push_notice(format!(
"Could not save '{source_key}': {error} — \
'{part_name}' updated in this document only"
));
WriteOutcome::Refused(format!("write failed: {error}"))
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum WriteOutcome {
Written,
Unchanged,
Refused(String),
}
pub use brep_render::engine_state::document_signature;