use std::{
fs,
path::{Path, PathBuf},
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::{Row, SqlitePool};
use uuid::Uuid;
use crate::error::BranchResult;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct EntityCounts {
pub memory_records: i64,
pub sessions: i64,
pub tool_outputs: i64,
}
impl EntityCounts {
pub fn total(&self) -> i64 {
self.memory_records + self.sessions + self.tool_outputs
}
pub async fn from_pool(pool: &SqlitePool) -> BranchResult<Self> {
Ok(Self {
memory_records: count_table(pool, "memory_records").await?,
sessions: count_table(pool, "sessions").await?,
tool_outputs: count_table(pool, "tool_outputs").await?,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SnapshotManifest {
pub branch_id: Uuid,
pub source_db_path: PathBuf,
pub snapshot_db_path: PathBuf,
pub source_hash: [u8; 32],
pub snapshot_hash: [u8; 32],
pub schema_version: u32,
pub created_at: DateTime<Utc>,
pub file_size_bytes: u64,
pub label: String,
pub entity_counts: EntityCounts,
pub sqlite_page_size: u32,
pub sqlite_page_count: u64,
}
impl SnapshotManifest {
pub fn save(&self, dir: &Path) -> BranchResult<()> {
fs::create_dir_all(dir)?;
let path = dir.join("manifest.json");
let contents = serde_json::to_vec_pretty(self)?;
fs::write(path, contents)?;
Ok(())
}
pub fn load(dir: &Path) -> BranchResult<Self> {
let path = dir.join("manifest.json");
let contents = fs::read(path)?;
Ok(serde_json::from_slice(&contents)?)
}
pub fn content_id(&self) -> String {
let mut output = String::with_capacity(self.snapshot_hash.len() * 2);
for byte in self.snapshot_hash {
use std::fmt::Write as _;
let _ = write!(&mut output, "{byte:02x}");
}
output
}
}
async fn count_table(pool: &SqlitePool, table: &str) -> BranchResult<i64> {
let query = format!("SELECT COUNT(*) AS count FROM {table}");
let row = sqlx::query(&query).fetch_one(pool).await?;
Ok(row.try_get::<i64, _>("count")?)
}