use crate::{dtob_ffi, html::Autolink, html::Macrolink, maps};
use std::process::Command;
use std::io::Write;
use tempfile::NamedTempFile;
use std::fs;
fn get_env_bin(env_var: &str, default: &str) -> String {
std::env::var(env_var).unwrap_or_else(|_| default.to_string())
}
fn flatten_refs_for_ccid(macrolinks: &[Macrolink], autolinks: &[Autolink]) -> Vec<u8> {
let mut blob = Vec::new();
for m in macrolinks {
blob.extend_from_slice(&xanadoc::trit_encode(&(m.ref_start as u32).to_be_bytes()));
blob.extend_from_slice(&xanadoc::trit_encode(&(m.ref_end as u32).to_be_bytes()));
}
for a in autolinks {
blob.extend_from_slice(&xanadoc::trit_encode(&(a.def_start as u32).to_be_bytes()));
blob.extend_from_slice(&xanadoc::trit_encode(&(a.def_end as u32).to_be_bytes()));
blob.extend_from_slice(&xanadoc::trit_encode(&(a.ref_start as u32).to_be_bytes()));
blob.extend_from_slice(&xanadoc::trit_encode(&(a.ref_end as u32).to_be_bytes()));
}
blob
}
pub fn generate_xvuid() -> [u8; 32] {
let mut arr = [0u8; 32];
if let Ok(mut f) = fs::File::open("/dev/urandom") {
use std::io::Read;
let _ = f.read_exact(&mut arr);
}
arr
}
fn stamp_ots(content: &[u8]) -> Option<Vec<u8>> {
let ots = get_env_bin("OTS_BIN", "/usr/local/bin/ots");
let mut tmp = NamedTempFile::new().ok()?;
tmp.write_all(content).ok()?;
let ots_path = format!("{}.ots", tmp.path().display());
let out = Command::new(&ots)
.arg("stamp")
.arg(tmp.path())
.output()
.ok()?;
if out.status.success() && Path::new(&ots_path).exists() {
let pf = fs::read(&ots_path).ok()?;
let _ = fs::remove_file(&ots_path);
return Some(pf);
}
None
}
use std::path::Path;
pub fn write_xanadoc_dif(
repo_path: &Path,
maps_path: &Path,
_category: &str,
_file_name: &str,
xvuid: [u8; 32],
title: &str,
word_count: usize,
content_body: &str,
macrolinks: Vec<Macrolink>,
autolinks: Vec<Autolink>,
doc_timestamp: u64,
debug: bool,
rebuild_maps: bool,
) -> Result<(), String> {
let content_bytes = content_body.as_bytes();
if debug { eprintln!("[write_xanadoc_dif] content_bytes len={}", content_bytes.len()); }
let cid = xanadoc::compute_cid(content_bytes);
if debug { eprintln!("[write_xanadoc_dif] CID={}", hex::encode(cid)); }
let sha256 = xanadoc::sha256(content_bytes);
if debug { eprintln!("[write_xanadoc_dif] SHA256={}", hex::encode(sha256)); }
let ots_proof = stamp_ots(content_bytes);
if debug { eprintln!("[write_xanadoc_dif] ots_proof present={}", ots_proof.is_some()); }
unsafe {
let meta = dtob_ffi::ffi_kvset_new();
if debug { eprintln!("[write_xanadoc_dif] allocated meta kvset"); }
let xvuid_val = dtob_ffi::ffi_raw_new(xvuid.as_ptr(), 32);
dtob_ffi::ffi_kvset_put(meta, b"xvuid\0".as_ptr() as *const i8, xvuid_val);
if debug { eprintln!("[write_xanadoc_dif] xvuid={}", hex::encode(xvuid)); }
let t_val = dtob_ffi::ffi_uint_new(doc_timestamp);
dtob_ffi::ffi_kvset_put(meta, b"timestamp\0".as_ptr() as *const i8, t_val);
if debug { eprintln!("[write_xanadoc_dif] timestamp={}", doc_timestamp); }
if let Some(pf) = &ots_proof {
let p_val = dtob_ffi::ffi_raw_new(pf.as_ptr(), pf.len());
dtob_ffi::ffi_kvset_put(meta, b"ots_proof\0".as_ptr() as *const i8, p_val);
if debug { eprintln!("[write_xanadoc_dif] ots_proof bytes={}", pf.len()); }
}
let wc = dtob_ffi::ffi_uint_new(word_count as u64);
dtob_ffi::ffi_kvset_put(meta, b"word_count\0".as_ptr() as *const i8, wc);
if debug { eprintln!("[write_xanadoc_dif] word_count={}", word_count); }
let t_str = title.as_bytes();
let tit = dtob_ffi::ffi_raw_new(t_str.as_ptr(), t_str.len());
dtob_ffi::ffi_kvset_put(meta, b"title\0".as_ptr() as *const i8, tit);
if debug { eprintln!("[write_xanadoc_dif] title={:?}", title); }
let refs = dtob_ffi::ffi_kvset_new();
let p_macros = dtob_ffi::ffi_array_new();
for _m in ¯olinks {
let dummy = dtob_ffi::ffi_array_new();
dtob_ffi::ffi_array_push(p_macros, dummy);
}
let p_autos = dtob_ffi::ffi_array_new();
for a in &autolinks {
let auto_entry = dtob_ffi::ffi_array_new();
dtob_ffi::ffi_array_push(auto_entry, dtob_ffi::ffi_uint_new(a.def_start as u64));
dtob_ffi::ffi_array_push(auto_entry, dtob_ffi::ffi_uint_new(a.def_end as u64));
dtob_ffi::ffi_array_push(auto_entry, dtob_ffi::ffi_uint_new(a.ref_start as u64));
dtob_ffi::ffi_array_push(auto_entry, dtob_ffi::ffi_uint_new(a.ref_end as u64));
dtob_ffi::ffi_array_push(p_autos, auto_entry);
}
dtob_ffi::ffi_kvset_put(refs, b"paleomacrolinks\0".as_ptr() as *const i8, p_macros);
dtob_ffi::ffi_kvset_put(refs, b"autolinks\0".as_ptr() as *const i8, p_autos);
let flat_refs = flatten_refs_for_ccid(¯olinks, &autolinks);
if debug { eprintln!("[write_xanadoc_dif] CCID flat_refs len={}, content len={}", flat_refs.len(), content_bytes.len()); }
let ccid = xanadoc::compute_ccid(&flat_refs, content_bytes);
if debug { eprintln!("[write_xanadoc_dif] CCID={}", hex::encode(ccid)); }
let dif_path = repo_path.join(format!("{}.dif", hex::encode(xvuid)));
if debug { eprintln!("[write_xanadoc_dif] dif_path={}", dif_path.display()); }
let ops = dtob_ffi::ffi_dif_ops_new();
let header_kv = dtob_ffi::ffi_kvset_new();
dtob_ffi::ffi_kvset_put(header_kv, b"magic\0".as_ptr() as *const i8, dtob_ffi::ffi_raw_new(b"08042026".as_ptr(), 8)); let ids = dtob_ffi::ffi_xanadoc_ids_new(ccid.as_ptr(), cid.as_ptr(), sha256.as_ptr());
dtob_ffi::ffi_kvset_put(header_kv, b"ids\0".as_ptr() as *const i8, ids);
dtob_ffi::ffi_kvset_put(header_kv, b"references\0".as_ptr() as *const i8, refs);
dtob_ffi::ffi_kvset_put(header_kv, b"neolinks\0".as_ptr() as *const i8, dtob_ffi::ffi_kvset_new());
dtob_ffi::ffi_kvset_put(header_kv, b"non-essential metadata\0".as_ptr() as *const i8, meta);
let mut header_len = 0;
let enc_ptr = dtob_ffi::ffi_encode_xanadoc_header(header_kv, &mut header_len);
let mut xanadoc_bytes = Vec::new();
if !enc_ptr.is_null() && header_len > 8 {
let s = std::slice::from_raw_parts(enc_ptr, header_len);
xanadoc_bytes.extend_from_slice(b"13032026");
xanadoc_bytes.extend_from_slice(&s[8..]);
libc::free(enc_ptr as *mut libc::c_void);
} else {
panic!("Failed to encode `.xanadoc` header block natively.");
}
xanadoc_bytes.push(0x0A);
xanadoc_bytes.extend_from_slice(content_bytes);
let (old_content, n) = if let Some(built) = crate::serve::build_content(&dif_path, None, debug) {
(Some(built.current_content), built.entry_count)
} else {
(None, 0)
};
let has_prev_content = old_content.is_some();
if debug { eprintln!("[write_xanadoc_dif] has_prev_content={} entry_count={}", has_prev_content, n); }
if n % crate::SNAPSHOT_INTERVAL == 0 || old_content.is_none() {
if debug { eprintln!("[write_xanadoc_dif] snapshot interval hit (n={}) — writing full content block", n); }
dtob_ffi::ffi_dif_ops_push_add(ops, xanadoc_bytes.as_ptr(), xanadoc_bytes.len());
} else {
if debug { eprintln!("[write_xanadoc_dif] delta mode — diffing against old content ({} bytes)", old_content.as_ref().unwrap().len()); }
let old_bytes = old_content.as_ref().unwrap();
dtob_ffi::ffi_diff_build(
old_bytes.as_ptr(), old_bytes.len(),
xanadoc_bytes.as_ptr(), xanadoc_bytes.len(),
ops
);
}
let entry = dtob_ffi::ffi_build_dif_entry(
ops
);
if debug { eprintln!("[write_xanadoc_dif] dif entry built, entry={:?}", entry); }
if has_prev_content {
if debug { eprintln!("[write_xanadoc_dif] appending to existing dif file"); }
let cpath = std::ffi::CString::new(dif_path.to_str().unwrap()).unwrap();
let null_fd = libc::open(b"/dev/null\0".as_ptr() as *const libc::c_char, libc::O_WRONLY);
let old_stderr = libc::dup(libc::STDERR_FILENO);
if null_fd >= 0 && old_stderr >= 0 {
libc::dup2(null_fd, libc::STDERR_FILENO);
}
let valid = dtob_ffi::ffi_verify_file_types(cpath.as_ptr(), 1);
if null_fd >= 0 && old_stderr >= 0 {
libc::dup2(old_stderr, libc::STDERR_FILENO);
libc::close(null_fd);
libc::close(old_stderr);
}
if debug { eprintln!("[write_xanadoc_dif] ffi_verify_file_types result={}", valid); }
if valid == 0 {
panic!("DIF Serialization Error: The custom types generated by Sculblog structurally diverge from the physical Types Header built into the file! Commit safely aborted to prevent data corruption.");
}
let mut final_len = 0;
let inner_enc = dtob_ffi::ffi_encode_chunk(entry, &mut final_len);
if debug { eprintln!("[write_xanadoc_dif] ffi_encode_chunk len={}", final_len); }
if inner_enc.is_null() {
panic!("DIF Serialization Error: The dynamically generated AST nodes strictly failed the compiler schema layout requirements! Commit aborted.");
}
let mut file_obj = fs::OpenOptions::new().read(true).write(true).open(&dif_path).unwrap();
let flen = file_obj.metadata().unwrap().len();
if debug { eprintln!("[write_xanadoc_dif] existing file len={}, trimming trailing 2 bytes", flen); }
if flen >= 2 {
file_obj.set_len(flen - 2).unwrap();
}
use std::io::{Seek, SeekFrom, Write};
file_obj.seek(SeekFrom::End(0)).unwrap();
let mut buf = Vec::new();
if final_len > 0 {
buf.extend_from_slice(std::slice::from_raw_parts(inner_enc, final_len));
}
buf.push(0xC0);
buf.push(0x01);
if debug { eprintln!("[write_xanadoc_dif] writing {} bytes (chunk + 0xC001 terminator)", buf.len()); }
file_obj.write_all(&buf).unwrap();
libc::free(inner_enc as *mut libc::c_void);
dtob_ffi::ffi_dtob_free(entry);
} else {
if debug { eprintln!("[write_xanadoc_dif] no prior file — writing fresh dif"); }
let root = dtob_ffi::ffi_array_new();
dtob_ffi::ffi_array_push(root, entry);
let mut out_len = 0;
let enc = dtob_ffi::ffi_encode_dif(root, &mut out_len);
if debug { eprintln!("[write_xanadoc_dif] fresh dif encoded len={}", out_len); }
if !enc.is_null() {
let s = std::slice::from_raw_parts(enc, out_len);
let _ = fs::write(&dif_path, s);
if debug { eprintln!("[write_xanadoc_dif] wrote {} bytes to {}", out_len, dif_path.display()); }
libc::free(enc as *mut libc::c_void);
}
dtob_ffi::ffi_dtob_free(root);
}
}
if rebuild_maps {
if debug { eprintln!("[write_xanadoc_dif] rebuilding memcache maps"); }
maps::rebuild_memcache_maps(&repo_path, maps_path);
}
if debug { eprintln!("[write_xanadoc_dif] done"); }
Ok(())
}