app
SOLIDWORKS Bridge
Public Made by Adomby adom
Launch, model and quit SOLIDWORKS from any Adom container. Nothing is ever lost.
main
Drew Owens
0.6.9 source: launch resilience, honest counts, ownership persistence, drain, quit ladder — 21 adversary passes, 303 te…
cf16621
1mo ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
//! The com host's own record of the documents it created, on disk.
//!
//! The host is killed and respawned by design — a wedged COM call cannot be
//! killed as a thread, which is the whole reason it lives in a child process.
//! But its in-memory record of what it created died with it, so the first thing
//! a respawn did was start refusing writes to the BRIDGE'S OWN documents:
//! fail-closed, but silently broken, and precisely after the recovery path the
//! architecture exists for.
//!
//! This file is written and read by the HOST, never by the server, which is what
//! keeps the host's veto independent. It lives beside the bridge's other scratch
//! state and `swx_uninstall` removes it.
use serde_json::Value;
fn path() -> std::path::PathBuf {
crate::scratch::root().join("com-host-created.json")
}
/// The SOLIDWORKS session the stored record belongs to, and its documents.
///
/// The session id is what makes this fail CLOSED rather than open. Without it
/// the file said only `[{"title":"Part1","path":null}]`, and it was cleared only
/// on a clean `stop` — so after a crash, a `swx_kill`, or the user quitting
/// SOLIDWORKS themselves, it survived and the user's NEXT `Part1` matched. The
/// server's record is session-scoped, so the write was still refused end to end;
/// but a second veto that can be wrong is not a second veto.
pub fn load_session() -> (Option<i64>, Vec<(String, Option<String>)>) {
let text = match std::fs::read_to_string(path()) {
Ok(t) => t,
Err(_) => return (None, Vec::new()),
};
let v: Value = match serde_json::from_str(&text) {
Ok(v) => v,
Err(_) => return (None, Vec::new()),
};
let session = v.get("session").and_then(|s| s.as_i64());
let docs = v
.get("documents")
.and_then(|d| d.as_array())
.map(|a| {
a.iter()
.filter_map(|e| {
let title = e.get("title")?.as_str()?.to_string();
let p = e
.get("path")
.and_then(|x| x.as_str())
.filter(|s| !s.is_empty())
.map(String::from);
Some((title, p))
})
.collect()
})
.unwrap_or_default();
(session, docs)
}
/// Documents this host created, as (title, path-once-saved). Legacy shape.
pub fn load() -> Vec<(String, Option<String>)> {
let text = match std::fs::read_to_string(path()) {
Ok(t) => t,
Err(_) => return Vec::new(),
};
let v: Value = match serde_json::from_str(&text) {
Ok(v) => v,
Err(_) => return Vec::new(),
};
v.as_array()
.map(|a| {
a.iter()
.filter_map(|e| {
let title = e.get("title")?.as_str()?.to_string();
let p = e
.get("path")
.and_then(|x| x.as_str())
.filter(|s| !s.is_empty())
.map(String::from);
Some((title, p))
})
.collect()
})
.unwrap_or_default()
}
/// Best effort: a host that cannot write its diary still refuses correctly for
/// the rest of its own life, and a respawn simply starts from nothing (which
/// fails closed).
pub fn save(created: &[(String, Option<String>)]) {
save_session(None, created)
}
/// Persist the record WITH the SOLIDWORKS session it belongs to.
pub fn save_session(session: Option<i64>, created: &[(String, Option<String>)]) {
let rows: Vec<Value> = created
.iter()
.map(|(t, p)| serde_json::json!({"title": t, "path": p}))
.collect();
let p = path();
if let Some(parent) = p.parent() {
let _ = std::fs::create_dir_all(parent);
}
// ATOMIC, like the server's half of the same record: this is the com
// host's independent veto, and a truncated file makes it refuse writes to
// the bridge's own documents — bug #5, from the host's side.
let _ = crate::launch::write_atomic(
&p,
&serde_json::json!({"session": session, "documents": Value::Array(rows)}).to_string(),
);
}
/// Forget everything — the SOLIDWORKS session ended, so its untitled numbering
/// restarts and no remembered title can be trusted.
pub fn clear() {
let _ = std::fs::remove_file(path());
}