use anyhow::{Context, Result};
use redb::{Database, ReadableTable, TableDefinition, WriteTransaction};
use std::path::{Path, PathBuf};
use std::sync::Arc;
const SESSIONS_TABLE: TableDefinition<&str, &[u8]> = TableDefinition::new("sessions");
const AGENTS_TABLE: TableDefinition<&str, &[u8]> = TableDefinition::new("agents");
const MESSAGES_TABLE: TableDefinition<&str, &[u8]> = TableDefinition::new("messages");
const METADATA_TABLE: TableDefinition<&str, &[u8]> = TableDefinition::new("metadata");
pub struct Store {
db: Arc<Database>,
data_dir: PathBuf,
}
impl Store {
pub fn new(data_dir: &Path) -> Result<Self> {
std::fs::create_dir_all(data_dir).context("Failed to create data directory")?;
let db_path = data_dir.join("rsclaw.redb");
let db = Database::create(&db_path).context("Failed to open database")?;
let store = Self {
db: Arc::new(db),
data_dir: data_dir.to_path_buf(),
};
store.init_tables()?;
Ok(store)
}
fn init_tables(&self) -> Result<()> {
let write_txn = self
.db
.begin_write()
.context("Failed to begin write transaction")?;
write_txn
.open_table(SESSIONS_TABLE)
.context("Failed to open sessions table")?;
write_txn
.open_table(AGENTS_TABLE)
.context("Failed to open agents table")?;
write_txn
.open_table(MESSAGES_TABLE)
.context("Failed to open messages table")?;
write_txn
.open_table(METADATA_TABLE)
.context("Failed to open metadata table")?;
write_txn.commit().context("Failed to commit transaction")?;
Ok(())
}
pub fn data_dir(&self) -> &Path {
&self.data_dir
}
pub fn db(&self) -> &Database {
&self.db
}
}
impl Store {
pub fn put(&self, table: TableDefinition<&str, &[u8]>, key: &str, value: &[u8]) -> Result<()> {
let write_txn = self.db.begin_write()?;
{
let mut table = write_txn.open_table(table)?;
table.insert(key, value)?;
}
write_txn.commit()?;
Ok(())
}
pub fn get(&self, table: TableDefinition<&str, &[u8]>, key: &str) -> Result<Option<Vec<u8>>> {
let read_txn = self.db.begin_read()?;
let table = read_txn.open_table(table)?;
match table.get(key)? {
Some(value) => Ok(Some(value.value().to_vec())),
None => Ok(None),
}
}
pub fn delete(&self, table: TableDefinition<&str, &[u8]>, key: &str) -> Result<bool> {
let write_txn = self.db.begin_write()?;
let existed = {
let mut table = write_txn.open_table(table)?;
table.remove(key)?.is_some()
};
write_txn.commit()?;
Ok(existed)
}
pub fn list_keys(&self, table: TableDefinition<&str, &[u8]>) -> Result<Vec<String>> {
let read_txn = self.db.begin_read()?;
let table = read_txn.open_table(table)?;
let mut keys = Vec::new();
for item in table.iter()? {
let (key, _) = item?;
keys.push(key.value().to_string());
}
Ok(keys)
}
}
impl Store {
pub fn save_session(&self, session_id: &str, data: &[u8]) -> Result<()> {
self.put(SESSIONS_TABLE, session_id, data)
}
pub fn get_session(&self, session_id: &str) -> Result<Option<Vec<u8>>> {
self.get(SESSIONS_TABLE, session_id)
}
pub fn delete_session(&self, session_id: &str) -> Result<bool> {
self.delete(SESSIONS_TABLE, session_id)
}
pub fn list_sessions(&self) -> Result<Vec<String>> {
self.list_keys(SESSIONS_TABLE)
}
}
impl Store {
pub fn save_agent(&self, agent_name: &str, data: &[u8]) -> Result<()> {
self.put(AGENTS_TABLE, agent_name, data)
}
pub fn get_agent(&self, agent_name: &str) -> Result<Option<Vec<u8>>> {
self.get(AGENTS_TABLE, agent_name)
}
pub fn delete_agent(&self, agent_name: &str) -> Result<bool> {
self.delete(AGENTS_TABLE, agent_name)
}
pub fn list_agents(&self) -> Result<Vec<String>> {
self.list_keys(AGENTS_TABLE)
}
}
impl Store {
pub fn save_message(&self, message_id: &str, data: &[u8]) -> Result<()> {
self.put(MESSAGES_TABLE, message_id, data)
}
pub fn get_message(&self, message_id: &str) -> Result<Option<Vec<u8>>> {
self.get(MESSAGES_TABLE, message_id)
}
pub fn delete_message(&self, message_id: &str) -> Result<bool> {
self.delete(MESSAGES_TABLE, message_id)
}
pub fn list_messages(&self) -> Result<Vec<String>> {
self.list_keys(MESSAGES_TABLE)
}
}
impl Store {
pub fn set_metadata(&self, key: &str, value: &[u8]) -> Result<()> {
self.put(METADATA_TABLE, key, value)
}
pub fn get_metadata(&self, key: &str) -> Result<Option<Vec<u8>>> {
self.get(METADATA_TABLE, key)
}
}
pub fn default_store() -> Result<Store> {
let home = dirs::home_dir().context("Cannot determine home directory")?;
let data_dir = home.join(".rsclaw").join("data");
Store::new(&data_dir)
}