use rete_core::v06::write::{WriteOpts, MIN_FILE_LEN};
pub(crate) fn writes_legacy() -> bool {
rete_core::build_record::writes_legacy()
}
pub(crate) fn finish(image: Vec<u8>) -> anyhow::Result<Vec<u8>> {
rete_core::build_record::finish_in_ram(image, None)
.map_err(|e| anyhow::anyhow!("writing 0x06: {e}"))
}
pub(crate) fn file_is_v06(path: &std::path::Path) -> anyhow::Result<bool> {
use std::io::Read;
let mut head = [0u8; 8];
let mut f = std::fs::File::open(path)?;
let n = f.read(&mut head)?;
Ok(rete_core::v06::open::is_v06(&head[..n]))
}
pub(crate) fn patch_record_in_place(path: &std::path::Path, info: &[u8]) -> anyhow::Result<bool> {
use std::io::{Read, Seek, SeekFrom, Write};
let mut f = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(path)?;
let mut head = vec![0u8; MIN_FILE_LEN as usize];
f.read_exact(&mut head)?;
let h = rete_core::v06::Header06::from_bytes(&head)?;
let r = h.build_range();
if r.len == 0 || info.len() as u64 > r.len {
return Ok(false);
}
let mut buf = info.to_vec();
buf.resize(r.len as usize, b' ');
f.seek(SeekFrom::Start(r.offset))?;
f.write_all(&buf)?;
f.sync_all()?;
Ok(true)
}
pub(crate) fn convert(
input: &str,
output: &str,
quad: Option<&str>,
label_index: bool,
) -> anyhow::Result<()> {
use rete_core::v06::write::QuadClustering;
if std::path::Path::new(input) == std::path::Path::new(output) {
anyhow::bail!("convert writes a new file: OUT must differ from IN");
}
let mut opts = WriteOpts::default();
match quad {
Some("graph") => opts.quad = QuadClustering::GraphMajor,
Some("order") => opts.quad = QuadClustering::OrderMajor,
Some("auto") | None => {}
Some(x) => anyhow::bail!("--quad-clustering must be auto, graph or order (got {x})"),
}
let reader = super::range_source::RangedSourceReader::open(input)?;
let head =
rete_core::RangeReader::read_at(&reader, 0, 8.min(rete_core::RangeReader::len(&reader)))?;
if rete_core::v06::open::is_v06(&head) {
anyhow::bail!("{input} is already format 0x06");
}
let started = std::time::Instant::now();
let labels = if label_index {
legacy_label_section(input)?
} else {
None
};
let tmp = std::path::PathBuf::from(format!("{output}.convert-tmp"));
let written = {
let mut out = std::io::BufWriter::with_capacity(8 << 20, std::fs::File::create(&tmp)?);
let w = match rete_core::v06::write::convert_with_labels(
&reader,
labels.as_deref(),
&mut out,
opts,
) {
Ok(w) => w,
Err(rete_core::format::FileError::Container(m)) if m.contains("kind 5") => {
drop(out);
let _ = std::fs::remove_file(&tmp);
return convert_per_graph(input, output, label_index);
}
Err(e) => {
drop(out);
let _ = std::fs::remove_file(&tmp);
return Err(anyhow::anyhow!("converting {input}: {e}"));
}
};
std::io::Write::flush(&mut out)?;
w
};
std::fs::rename(&tmp, output)?;
let in_len = rete_core::RangeReader::len(&reader);
eprintln!(
"convert: wrote {output} — format 0x06, {} bytes ({:+.2}% vs {} bytes), content hash {}, {:.1}s",
written.header.file_len,
(written.header.file_len as f64 - in_len as f64) * 100.0 / in_len.max(1) as f64,
in_len,
hex16(&written.header.content_hash),
started.elapsed().as_secs_f64()
);
Ok(())
}
fn legacy_label_section(input: &str) -> anyhow::Result<Option<Vec<u8>>> {
let reader = super::range_source::RangedSourceReader::open(input)?;
let rete = rete_core::Rete::open_ranged_lazy(reader)?;
if rete.has_label_index() || rete.pyramid().is_none_or(|p| p.label_index.is_empty()) {
return Ok(None);
}
let legacy = rete.pyramid().map_or(0, |p| p.label_index.len());
let started = std::time::Instant::now();
let sec = rete_core::v06::write::label_section(&rete);
if sec.is_empty() {
return Ok(None);
}
eprintln!(
"convert: {input} has only the pyramid's legacy label block ({legacy} entries); built \
the label index ({} bytes) in {:.1}s — --no-label-index skips it",
sec.len(),
started.elapsed().as_secs_f64()
);
Ok(Some(sec))
}
fn convert_per_graph(input: &str, output: &str, label_index: bool) -> anyhow::Result<()> {
eprintln!(
"convert: {input} stores its named graphs per graph (kind 5); re-encoding them as a quad index"
);
let bytes = std::fs::read(input)?;
let rete = rete_core::Rete::open(&bytes)?;
let card = rete.metadata().map(<[u8]>::to_vec);
let mut quads: Vec<rete_core::format::RawQuad> = rete
.dump(None)
.into_iter()
.map(|(s, p, o)| (s, p, o, None))
.collect();
let named: Vec<String> = rete.graph_names().iter().map(|g| g.to_string()).collect();
for g in &named {
for (s, p, o) in rete.dump(Some(g)) {
quads.push((s, p, o, Some(g.clone())));
}
}
let scope = rete.text_index_scope();
let legacy_labels = rete.pyramid().is_some_and(|p| !p.label_index.is_empty());
let mut opts = rete_core::ingest::AssembleOptions::sectioned();
opts.graph_layout = rete_core::GraphLayout::Quad;
opts.with_pyramid = rete.header().pyramid_meta_len > 0;
opts.with_text_index = rete.has_text_index();
opts.text_index_scope = scope.as_ref();
opts.label_index = rete.has_label_index() || (label_index && legacy_labels);
opts.perms = rete.header().perms;
let (image, stats) = rete_core::ingest::assemble_dataset_opts(quads, &opts, |_, _| {
card.clone().unwrap_or_default()
});
let image = finish_recorded(image, "convert", &stats)?;
std::fs::write(output, &image)?;
eprintln!(
"convert: wrote {output} — format 0x06, {} bytes",
image.len()
);
Ok(())
}
fn hex16(b: &[u8; 16]) -> String {
b.iter().map(|x| format!("{x:02x}")).collect()
}
pub(crate) fn recard_file(
path: &str,
card: rete_core::v06::write::NewCard<'_>,
record: Option<&[u8]>,
) -> anyhow::Result<(rete_core::v06::Header06, bool)> {
use rete_core::v06::write::{commit_header, recard_in_place};
use std::io::Write;
let mut f = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(path)
.map_err(|e| anyhow::anyhow!("opening {path} for writing: {e}"))?;
if let Some(h) =
recard_in_place(&mut f, card, record).map_err(|e| anyhow::anyhow!("{path}: {e}"))?
{
f.sync_data()?;
commit_header(&mut f, &h).map_err(|e| anyhow::anyhow!("{path}: {e}"))?;
f.sync_all()?;
return Ok((h, false));
}
drop(f);
let tmp = std::path::PathBuf::from(format!("{path}.recard-tmp"));
let h = {
let reader = super::range_source::LocalRangeReader::open(path)?;
let mut out = std::io::BufWriter::with_capacity(8 << 20, std::fs::File::create(&tmp)?);
let written =
rete_core::v06::write::rewrite_card_stream(&reader, &mut out, card, record, &mut |n| {
rete_core::build_progress::set_done(n)
});
let h = match written {
Ok(h) => h,
Err(e) => {
drop(out);
let _ = std::fs::remove_file(&tmp);
anyhow::bail!("rewriting {path}: {e}");
}
};
out.flush()?;
out.into_inner()
.map_err(|e| anyhow::anyhow!("flushing {}: {e}", tmp.display()))?
.sync_all()?;
h
};
std::fs::rename(&tmp, path)?;
Ok((h, true))
}
pub(crate) fn finish_recorded(
image: Vec<u8>,
command: &str,
stats: &rete_core::ingest::BuildStats,
) -> anyhow::Result<Vec<u8>> {
use rete_core::build_record as br;
let record = br::to_json(
&br::Record {
front_end: br::front_end::CLI.into(),
front_end_version: env!("CARGO_PKG_VERSION").into(),
built_at: br::timestamp(),
options: br::Options {
command: command.into(),
..Default::default()
},
inputs: Vec::new(),
statements_read: None,
},
Some(stats),
);
rete_core::build_record::finish_in_ram(image, Some(&record))
.map_err(|e| anyhow::anyhow!("writing 0x06: {e}"))
}