File size: 8,354 Bytes
3374e90 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | use anyhow::{Context, Result};
use bex_types::plugin_info::PluginInfo;
use redb::{Database, ReadableTable, TableDefinition};
use std::path::Path;
use std::sync::Arc;
const KV_TABLE: TableDefinition<&str, &[u8]> = TableDefinition::new("kv");
const SECRETS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("secrets");
const PLUGINS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("plugins");
const WASM_TABLE: TableDefinition<&str, &[u8]> = TableDefinition::new("wasm_blobs");
const MANIFEST_TABLE: TableDefinition<&str, &str> = TableDefinition::new("manifests");
fn kv_key(pid: &str, key: &str) -> String {
format!("{}\x00{}", pid, key)
}
pub struct BexDb {
db: Arc<Database>,
}
impl BexDb {
pub fn open(data_dir: &Path) -> Result<Self> {
std::fs::create_dir_all(data_dir)?;
let db_path = data_dir.join("bex.redb");
// Compact on open if DB file is > 10 MB (before Arc wrapping,
// since compact() requires &mut Database)
let should_compact = std::fs::metadata(&db_path)
.map(|m| m.len() > 10 * 1024 * 1024)
.unwrap_or(false);
let mut db = Database::create(&db_path).context("failed to open redb")?;
if should_compact {
tracing::info!("Compacting database (file > 10 MB)...");
if let Err(e) = db.compact() {
tracing::warn!("Database compaction failed: {}", e);
}
}
let db = Arc::new(db);
let write_txn = db.begin_write()?;
write_txn.open_table(KV_TABLE)?;
write_txn.open_table(SECRETS_TABLE)?;
write_txn.open_table(PLUGINS_TABLE)?;
write_txn.open_table(WASM_TABLE)?;
write_txn.open_table(MANIFEST_TABLE)?;
write_txn.commit()?;
Ok(Self { db })
}
pub fn kv_set(&self, plugin_id: &str, key: &str, value: &[u8]) -> Result<bool> {
let k = kv_key(plugin_id, key);
let write_txn = self.db.begin_write()?;
let mut table = write_txn.open_table(KV_TABLE)?;
table.insert(k.as_str(), value)?;
drop(table);
write_txn.commit()?;
Ok(true)
}
pub fn kv_get(&self, plugin_id: &str, key: &str) -> Result<Option<Vec<u8>>> {
let k = kv_key(plugin_id, key);
let read_txn = self.db.begin_read()?;
let table = read_txn.open_table(KV_TABLE)?;
let result = table.get(k.as_str())?.map(|v| v.value().to_vec());
Ok(result)
}
pub fn kv_remove(&self, plugin_id: &str, key: &str) -> Result<bool> {
let k = kv_key(plugin_id, key);
let write_txn = self.db.begin_write()?;
let mut table = write_txn.open_table(KV_TABLE)?;
let existed = table.remove(k.as_str())?.is_some();
drop(table);
write_txn.commit()?;
Ok(existed)
}
pub fn kv_keys(&self, plugin_id: &str, prefix: &str) -> Result<Vec<String>> {
let start = kv_key(plugin_id, prefix);
let read_txn = self.db.begin_read()?;
let table = read_txn.open_table(KV_TABLE)?;
let range = table.range(start.as_str()..)?;
let mut result = Vec::new();
for item in range {
let (k, _) = item?;
let key_str = k.value();
if let Some(pos) = key_str.find('\0') {
let pid = &key_str[..pos];
let key = &key_str[pos + 1..];
if pid == plugin_id && key.starts_with(prefix) {
result.push(key.to_string());
}
}
}
Ok(result)
}
pub fn secret_set(&self, plugin_id: &str, key: &str, value: &str) -> Result<()> {
let k = kv_key(plugin_id, key);
let write_txn = self.db.begin_write()?;
let mut table = write_txn.open_table(SECRETS_TABLE)?;
table.insert(k.as_str(), value)?;
drop(table);
write_txn.commit()?;
Ok(())
}
pub fn secret_get(&self, plugin_id: &str, key: &str) -> Result<Option<String>> {
let k = kv_key(plugin_id, key);
let read_txn = self.db.begin_read()?;
let table = read_txn.open_table(SECRETS_TABLE)?;
let result = table.get(k.as_str())?.map(|v| v.value().to_string());
Ok(result)
}
pub fn secret_remove(&self, plugin_id: &str, key: &str) -> Result<bool> {
let k = kv_key(plugin_id, key);
let write_txn = self.db.begin_write()?;
let mut table = write_txn.open_table(SECRETS_TABLE)?;
let existed = table.remove(k.as_str())?.is_some();
drop(table);
write_txn.commit()?;
Ok(existed)
}
pub fn secret_keys(&self, plugin_id: &str, prefix: &str) -> Result<Vec<String>> {
let start = kv_key(plugin_id, prefix);
let read_txn = self.db.begin_read()?;
let table = read_txn.open_table(SECRETS_TABLE)?;
let range = table.range(start.as_str()..)?;
let mut result = Vec::new();
for item in range {
let (k, _) = item?;
let key_str = k.value();
if let Some(pos) = key_str.find('\0') {
let pid = &key_str[..pos];
let key = &key_str[pos + 1..];
if pid == plugin_id && key.starts_with(prefix) {
result.push(key.to_string());
}
}
}
Ok(result)
}
pub fn save_plugin_info(&self, info: &PluginInfo) -> Result<()> {
let json = serde_json::to_string(info)?;
let write_txn = self.db.begin_write()?;
let mut table = write_txn.open_table(PLUGINS_TABLE)?;
table.insert(info.id.as_str(), json.as_str())?;
drop(table);
write_txn.commit()?;
Ok(())
}
pub fn get_plugin_info(&self, id: &str) -> Result<Option<PluginInfo>> {
let read_txn = self.db.begin_read()?;
let table = read_txn.open_table(PLUGINS_TABLE)?;
let result = match table.get(id)? {
Some(json) => Some(serde_json::from_str::<PluginInfo>(json.value())?),
None => None,
};
Ok(result)
}
pub fn list_plugins(&self) -> Result<Vec<PluginInfo>> {
let read_txn = self.db.begin_read()?;
let table = read_txn.open_table(PLUGINS_TABLE)?;
let mut result = Vec::new();
for item in table.iter()? {
let (_, json) = item?;
if let Ok(info) = serde_json::from_str::<PluginInfo>(json.value()) {
result.push(info);
}
}
Ok(result)
}
pub fn remove_plugin(&self, id: &str) -> Result<bool> {
let write_txn = self.db.begin_write()?;
let mut table = write_txn.open_table(PLUGINS_TABLE)?;
let existed = table.remove(id)?.is_some();
drop(table);
// Also remove WASM blob and manifest
let mut wasm_table = write_txn.open_table(WASM_TABLE)?;
wasm_table.remove(id)?;
drop(wasm_table);
let mut manifest_table = write_txn.open_table(MANIFEST_TABLE)?;
manifest_table.remove(id)?;
drop(manifest_table);
write_txn.commit()?;
Ok(existed)
}
/// Store the WASM blob for a plugin
pub fn save_wasm_blob(&self, id: &str, wasm: &[u8]) -> Result<()> {
let write_txn = self.db.begin_write()?;
let mut table = write_txn.open_table(WASM_TABLE)?;
table.insert(id, wasm)?;
drop(table);
write_txn.commit()?;
Ok(())
}
/// Retrieve the WASM blob for a plugin
pub fn get_wasm_blob(&self, id: &str) -> Result<Option<Vec<u8>>> {
let read_txn = self.db.begin_read()?;
let table = read_txn.open_table(WASM_TABLE)?;
Ok(table.get(id)?.map(|v| v.value().to_vec()))
}
/// Store the manifest YAML for a plugin
pub fn save_manifest(&self, id: &str, manifest_yaml: &str) -> Result<()> {
let write_txn = self.db.begin_write()?;
let mut table = write_txn.open_table(MANIFEST_TABLE)?;
table.insert(id, manifest_yaml)?;
drop(table);
write_txn.commit()?;
Ok(())
}
/// Retrieve the manifest YAML for a plugin
pub fn get_manifest(&self, id: &str) -> Result<Option<String>> {
let read_txn = self.db.begin_read()?;
let table = read_txn.open_table(MANIFEST_TABLE)?;
Ok(table.get(id)?.map(|v| v.value().to_string()))
}
}
|