use qmd::Store;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{LazyLock, Mutex};
static STORES: LazyLock<Mutex<HashMap<PathBuf, &'static Mutex<Store>>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
pub fn get_store() -> Result<&'static Mutex<Store>, String> {
let db_path = memory_dir().join("memory.db");
{
let map = STORES
.lock()
.map_err(|e| format!("Store registry lock poisoned: {e}"))?;
if let Some(store) = map.get(&db_path) {
return Ok(*store);
}
}
let store = open_store(&db_path)?;
let mut map = STORES
.lock()
.map_err(|e| format!("Store registry lock poisoned: {e}"))?;
Ok(map.entry(db_path).or_insert(store))
}
fn open_store(db_path: &Path) -> Result<&'static Mutex<Store>, String> {
let store = build_store(db_path)?;
Ok(Box::leak(Box::new(Mutex::new(store))))
}
fn build_store(db_path: &Path) -> Result<Store, String> {
{
let db_path = db_path.to_path_buf();
if let Some(parent) = db_path.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| format!("Failed to create memory dir: {e}"))?;
}
let store =
Store::open(&db_path).map_err(|e| format!("Failed to open memory store: {e}"))?;
if super::vector_enabled() {
let dims = super::embedding_dimensions();
store
.ensure_vector_table(dims)
.map_err(|e| format!("Failed to create vector table: {e}"))?;
tracing::info!("Vector table created with {dims} dimensions");
}
tracing::info!(
"Memory qmd store ready at {} (vector: {})",
db_path.display(),
if super::vector_enabled() {
"enabled"
} else {
"disabled"
}
);
Ok(store)
}
}
pub(crate) fn memory_dir() -> PathBuf {
crate::config::opencrabs_home().join("memory")
}
pub(crate) fn clear_skipped_placeholders() -> Result<usize, String> {
let db_path = memory_dir().join("memory.db");
if !db_path.exists() {
return Ok(0);
}
let conn = rusqlite::Connection::open(&db_path)
.map_err(|e| format!("Failed to open store for placeholder sweep: {e}"))?;
conn.busy_timeout(std::time::Duration::from_secs(5))
.map_err(|e| format!("Failed to set busy timeout: {e}"))?;
conn.execute(
"DELETE FROM vectors_vec WHERE hash_seq IN (
SELECT hash || '_' || seq FROM content_vectors WHERE model = 'skipped-too-large'
)",
[],
)
.map_err(|e| format!("Failed to clear placeholder vectors: {e}"))?;
let removed = conn
.execute(
"DELETE FROM content_vectors WHERE model = 'skipped-too-large'",
[],
)
.map_err(|e| format!("Failed to clear placeholder rows: {e}"))?;
if removed > 0 {
tracing::info!(
"Cleared {removed} skipped-too-large embedding placeholders; \
those documents will be chunked and embedded on the next backfill"
);
}
Ok(removed)
}