#![cfg(feature = "code")]
use std::collections::HashMap;
use std::num::NonZeroUsize;
use std::path::PathBuf;
use std::sync::{Arc, OnceLock, Weak};
use lru::LruCache;
use parking_lot::Mutex;
use crate::config::{Durability, SqliteTuning};
use crate::errors::{MCSError, Result};
use crate::kg::GraphHandle;
pub const DEFAULT_PROJECT: &str = "default";
const MAX_PROJECT_LEN: usize = 64;
const MAX_WARM_HANDLES: usize = 16;
struct RegistryConfig {
base: PathBuf,
durability: Durability,
tuning: SqliteTuning,
lru_cache: NonZeroUsize,
read_pool_size: usize,
}
struct Inner {
live: HashMap<String, Weak<GraphHandle>>,
warm: LruCache<String, Arc<GraphHandle>>,
}
static CONFIG: OnceLock<RegistryConfig> = OnceLock::new();
static INNER: OnceLock<Mutex<Inner>> = OnceLock::new();
pub fn init(
base: PathBuf,
durability: Durability,
tuning: SqliteTuning,
lru_cache: NonZeroUsize,
read_pool_size: usize,
) {
let _ = std::fs::create_dir_all(&base);
let _ = CONFIG.set(RegistryConfig {
base,
durability,
tuning,
lru_cache,
read_pool_size,
});
let warm = LruCache::new(NonZeroUsize::new(MAX_WARM_HANDLES).expect("MAX_WARM_HANDLES > 0"));
let _ = INNER.set(Mutex::new(Inner {
live: HashMap::new(),
warm,
}));
}
pub fn validate_project(project: &str) -> Result<()> {
let ok = !project.is_empty()
&& project.len() <= MAX_PROJECT_LEN
&& project
.bytes()
.all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-');
if ok {
Ok(())
} else {
Err(MCSError::InvalidParams(format!(
"invalid project '{project}': use 1-{MAX_PROJECT_LEN} chars of [A-Za-z0-9_-]"
)))
}
}
pub fn resolve(project: &str) -> Result<Arc<GraphHandle>> {
validate_project(project)?;
let cfg = CONFIG.get().ok_or_else(|| {
MCSError::InvalidParams("code registry not initialized (start the server with --code)".into())
})?;
let inner = INNER.get().expect("registry inner set alongside config");
let mut g = inner.lock();
if let Some(existing) = g.live.get(project).and_then(Weak::upgrade) {
g.warm.put(project.to_string(), Arc::clone(&existing));
return Ok(existing);
}
g.live.retain(|_, w| w.strong_count() > 0);
let path = cfg.base.join(format!("{project}.code.db"));
let handle = Arc::new(GraphHandle::new(
&path,
cfg.durability,
cfg.tuning,
cfg.lru_cache,
cfg.read_pool_size,
)?);
g.live.insert(project.to_string(), Arc::downgrade(&handle));
g.warm.put(project.to_string(), Arc::clone(&handle));
Ok(handle)
}