#![forbid(unsafe_code)]
use anyhow::Context as _;
use kcode_session_history::{
Session,
chatend::{BoxId, EventId, PendingId},
};
use serde_json::{Value, json};
const BOX_TEXT_OBJECT_SOURCE: &str = "kennedy-box-text";
const BOX_TEXT_MEDIA_TYPE: &str = "text/plain; charset=utf-8";
#[derive(Debug, Eq, PartialEq)]
struct BoxTextObject {
box_id: BoxId,
pending_id: PendingId,
reused: bool,
}
pub fn stage_box_text_objects(
journal: &mut Session,
box_ids: &[BoxId],
recorded_at: &str,
) -> anyhow::Result<String> {
let selections = box_ids
.iter()
.map(|box_id| {
let state = journal
.state()
.box_state(*box_id)
.with_context(|| format!("box {box_id} does not exist"))?;
anyhow::ensure!(state.active, "box {box_id} is not active");
anyhow::ensure!(
!state.canonical.content.text.is_empty(),
"box {box_id} has no text content"
);
Ok((
*box_id,
state.name.clone(),
state.canonical.event_id,
state.canonical.content.text.clone(),
))
})
.collect::<anyhow::Result<Vec<_>>>()?;
let mut objects = Vec::with_capacity(selections.len());
for (box_id, box_name, canonical_event_id, text) in selections {
if let Some(pending_id) = existing_box_text_object(journal, box_id, canonical_event_id) {
objects.push(BoxTextObject {
box_id,
pending_id,
reused: true,
});
continue;
}
let pending_id = journal.stage_object(
recorded_at,
BOX_TEXT_MEDIA_TYPE,
Some(format!("box-{box_id}.txt")),
json!({
"source":BOX_TEXT_OBJECT_SOURCE,
"boxId":box_id.0,
"boxName":box_name,
"canonicalEventId":canonical_event_id.0,
}),
text.as_bytes(),
)?;
objects.push(BoxTextObject {
box_id,
pending_id,
reused: false,
});
}
Ok(render_box_text_objects(&objects))
}
fn existing_box_text_object(
journal: &Session,
box_id: BoxId,
canonical_event_id: EventId,
) -> Option<PendingId> {
journal
.objects()
.iter()
.find(|(_, location)| {
let transport = &location.metadata.transport;
transport.get("source").and_then(Value::as_str) == Some(BOX_TEXT_OBJECT_SOURCE)
&& transport.get("boxId").and_then(Value::as_u64) == Some(box_id.0)
&& transport.get("canonicalEventId").and_then(Value::as_u64)
== Some(canonical_event_id.0)
})
.map(|(pending_id, _)| pending_id.clone())
}
fn render_box_text_objects(objects: &[BoxTextObject]) -> String {
let mut text = String::from("Box text objects:");
for object in objects {
text.push_str(&format!("\nBox {}: {}", object.box_id, object.pending_id));
if object.reused {
text.push_str(" (already staged)");
}
}
text.push_str(
"\nThese pending object references resolve to canonical object IDs when the logical session commits.",
);
text
}