use crate::cli::Cli;
use crate::context::{ExecutionMode, GriteContext};
use crate::output::{output_success, print_human};
use libgrite_core::GriteError;
use libgrite_git::{SnapshotManager, WalManager};
use libgrite_ipc::{IpcClient, IpcCommand, IpcRequest};
use serde::Serialize;
const REBUILD_TIMEOUT_MS: u64 = 300_000;
#[derive(Serialize)]
struct RebuildOutput {
wal_head: Option<String>,
event_count: usize,
from_snapshot: Option<String>,
snapshot_events: Option<usize>,
}
pub fn run(cli: &Cli, use_snapshot: bool) -> Result<(), GriteError> {
let ctx = GriteContext::resolve(cli)?;
match ctx.execution_mode(cli.no_daemon) {
ExecutionMode::Daemon { endpoint, .. } => {
rebuild_via_daemon(cli, &ctx, &endpoint)
}
ExecutionMode::Blocked { lock } => Err(GriteError::DbBusy(format!(
"Store is locked by pid {} (expires in {}s). \
Try again later or run 'grite daemon stop' first.",
lock.pid,
lock.time_remaining_ms() / 1000
))),
ExecutionMode::Local => {
let store = ctx.open_store()?;
let git_dir = ctx.repo_root().join(".git");
do_rebuild(cli, &store, &git_dir, use_snapshot)
}
}
}
fn rebuild_via_daemon(cli: &Cli, ctx: &GriteContext, endpoint: &str) -> Result<(), GriteError> {
let mut client = IpcClient::connect_with_timeout(endpoint, REBUILD_TIMEOUT_MS)
.map_err(|e| GriteError::Internal(format!("Failed to connect to daemon: {}", e)))?;
let request = IpcRequest::new(
uuid::Uuid::new_v4().to_string(),
ctx.repo_root().to_string_lossy().to_string(),
ctx.actor_id.clone(),
ctx.data_dir.to_string_lossy().to_string(),
IpcCommand::Rebuild,
);
let response = client
.send(&request)
.map_err(|e| GriteError::Internal(format!("Rebuild via daemon failed: {}", e)))?;
if response.ok {
if let Some(data) = &response.data {
if cli.json {
println!("{}", data);
} else if !cli.quiet {
if let Ok(json) = serde_json::from_str::<serde_json::Value>(data) {
let count = json
.get("event_count")
.and_then(|v| v.as_u64())
.unwrap_or(0);
print_human(cli, &format!("Rebuilt {} events (via daemon)", count));
}
}
}
Ok(())
} else {
let msg = response
.error
.map(|e| e.message)
.unwrap_or_else(|| "unknown error".to_string());
Err(GriteError::Internal(format!(
"Daemon rebuild failed: {}",
msg
)))
}
}
fn do_rebuild(
cli: &Cli,
store: &libgrite_core::LockedStore,
git_dir: &std::path::Path,
use_snapshot: bool,
) -> Result<(), GriteError> {
if use_snapshot {
let snap_mgr = SnapshotManager::open(git_dir)?;
let wal_mgr = WalManager::open(git_dir)?;
let snapshots = snap_mgr.list()?;
if snapshots.is_empty() {
print_human(cli, "No snapshots found, falling back to full rebuild");
let stats = store.rebuild()?;
output_success(
cli,
RebuildOutput {
wal_head: wal_mgr.head().ok().flatten().map(|oid| oid.to_string()),
event_count: stats.event_count,
from_snapshot: None,
snapshot_events: None,
},
);
return Ok(());
}
let latest = &snapshots[0]; print_human(cli, &format!("Loading from snapshot: {}", latest.ref_name));
let snapshot_events = snap_mgr.read(latest.oid)?;
let snap_count = snapshot_events.len();
let stats = store.rebuild_from_events(&snapshot_events)?;
print_human(cli, &format!("Rebuilt from {} snapshot events", snap_count));
output_success(
cli,
RebuildOutput {
wal_head: wal_mgr.head().ok().flatten().map(|oid| oid.to_string()),
event_count: stats.event_count,
from_snapshot: Some(latest.ref_name.clone()),
snapshot_events: Some(snap_count),
},
);
} else {
let wal_head = WalManager::open(git_dir)
.ok()
.and_then(|wal| wal.head().ok().flatten());
let stats = store.rebuild()?;
output_success(
cli,
RebuildOutput {
wal_head: wal_head.map(|oid| oid.to_string()),
event_count: stats.event_count,
from_snapshot: None,
snapshot_events: None,
},
);
}
Ok(())
}