use anyhow::{Context, Result};
use trusty_common::memory_core::palace::Palace;
use trusty_common::memory_core::retrieval::PalaceHandle;
use trusty_common::memory_core::store::room_plan::{plan_rooms, RoomPlanAction, RoomPlanEntry};
use trusty_common::memory_core::store::{backfill_rooms, OpenIntent};
use trusty_common::memory_core::{MaintenanceLease, PalaceRegistry};
use super::maintenance_gate::open_purging_under_lease;
#[derive(Debug, Clone)]
pub struct PalaceRoomAudit {
pub palace_id: String,
pub entries: Vec<RoomPlanEntry>,
pub inserted: Option<usize>,
pub error: Option<String>,
}
impl PalaceRoomAudit {
pub fn pending(&self) -> usize {
self.entries.iter().filter(|e| e.would_insert()).count()
}
}
pub async fn handle_rooms_backfill(palace: Option<String>, apply: bool) -> Result<()> {
let data_dir = trusty_common::resolve_data_dir("trusty-memory")
.context("resolve trusty-memory data dir")?;
let data_root = crate::resolve_palace_registry_dir(data_dir);
let audits = audit_palaces(&data_root, palace.as_deref(), apply)?;
print_audits(&audits, apply);
Ok(())
}
fn print_audits(audits: &[PalaceRoomAudit], apply: bool) {
let mode = if apply { "apply" } else { "dry-run" };
let (mut rooms, mut pending, mut written, mut errors) = (0usize, 0usize, 0usize, 0usize);
for audit in audits {
if let Some(e) = &audit.error {
errors += 1;
eprintln!("[error] palace={} {e}", audit.palace_id);
continue;
}
println!("palace={}", audit.palace_id);
for entry in &audit.entries {
rooms += 1;
let (verb, source) = match &entry.action {
RoomPlanAction::Insert { source, .. } => ("register", format!("{source:?}")),
RoomPlanAction::Registered { resolved, .. } => (
"keep",
if *resolved {
"existing"
} else {
"existing/unresolved"
}
.to_string(),
),
};
println!(
" {:<8} {} label={:<28} drawers={:<6} via={}",
verb,
entry.room_id,
entry.label(),
entry.drawer_count,
source,
);
}
pending += audit.pending();
written += audit.inserted.unwrap_or(0);
}
if apply {
println!(
"rooms {mode}: {} palace(s), {rooms} room(s), {written} registered, {errors} error(s)",
audits.len()
);
} else {
println!(
"rooms {mode}: {} palace(s), {rooms} room(s), {pending} would be registered, {errors} error(s) \
— nothing was written; re-run with --apply",
audits.len()
);
}
}
pub fn audit_palaces(
data_root: &std::path::Path,
palace_filter: Option<&str>,
apply: bool,
) -> Result<Vec<PalaceRoomAudit>> {
let palaces = PalaceRegistry::list_palaces(data_root).unwrap_or_default();
let lease = MaintenanceLease::new(data_root);
let mut out = Vec::new();
for palace in palaces {
let id = palace.id.0.clone();
if palace_filter.is_some_and(|f| f != id) {
continue;
}
out.push(
audit_one(&palace, apply, &lease).unwrap_or_else(|e| PalaceRoomAudit {
palace_id: id,
entries: Vec::new(),
inserted: None,
error: Some(format!("{e:#}")),
}),
);
}
Ok(out)
}
fn audit_one(palace: &Palace, apply: bool, lease: &MaintenanceLease) -> Result<PalaceRoomAudit> {
let handle = if apply {
open_purging_under_lease(palace, OpenIntent::Writer, lease)?
} else {
PalaceHandle::open_with_intent_purging(palace, OpenIntent::ReadOnlyClient, false)
.with_context(|| format!("open palace {}", palace.id))?
};
let drawers = handle.drawers.read().clone();
let entries = plan_rooms(&handle.kg, &drawers).context("plan rooms")?;
let inserted = if apply {
Some(
backfill_rooms(&handle.kg, &drawers)
.context("apply room backfill")?
.inserted,
)
} else {
None
};
Ok(PalaceRoomAudit {
palace_id: palace.id.0.clone(),
entries,
inserted,
error: None,
})
}
#[cfg(test)]
#[path = "rooms_tests.rs"]
mod tests;