use std::path::{Path, PathBuf};
#[cfg(feature = "sqlite")]
use std::time::Duration;
#[cfg(feature = "sqlite")]
const DEFAULT_SQL_BUSY_TIMEOUT_MS: u64 = 5000;
#[cfg(feature = "sqlite")]
const DEFAULT_SQL_QUERY_TIMEOUT_MS: u64 = 5000;
#[cfg(feature = "sqlite")]
const DEFAULT_SQL_JOURNAL_MODE: &str = "WAL";
const DEFAULT_BUS_CAPACITY: usize = 64;
const DEFAULT_TASK_GRACE_MS: u64 = 1000;
pub fn base_dir() -> Result<PathBuf, String> {
if let Some(v) = std::env::var_os("AGENT_BLOCK_HOME") {
return Ok(PathBuf::from(v));
}
let home = std::env::var_os("HOME").ok_or_else(|| "HOME env var not set".to_string())?;
Ok(PathBuf::from(home).join(".agent-block"))
}
#[cfg(feature = "sqlite")]
pub fn kv_path() -> Result<PathBuf, String> {
if let Some(v) = std::env::var_os("AGENT_BLOCK_KV_PATH") {
return Ok(PathBuf::from(v));
}
Ok(base_dir()?.join("kv.sqlite"))
}
#[cfg(feature = "sqlite")]
pub fn sql_path() -> Result<PathBuf, String> {
if let Some(v) = std::env::var_os("AGENT_BLOCK_SQL_PATH") {
return Ok(PathBuf::from(v));
}
Ok(base_dir()?.join("db.sqlite"))
}
#[cfg(feature = "sqlite")]
pub fn ts_path() -> Result<PathBuf, String> {
if let Some(v) = std::env::var_os("AGENT_BLOCK_TS_PATH") {
return Ok(PathBuf::from(v));
}
Ok(base_dir()?.join("ts.sqlite"))
}
pub fn project_slug(project_root: &Path) -> String {
project_root
.to_string_lossy()
.chars()
.map(|c| {
if c == '/' || c == std::path::MAIN_SEPARATOR {
'-'
} else {
c
}
})
.collect()
}
pub fn knl_path(project_root: &Path) -> Result<PathBuf, String> {
resolve_knl_path(
std::env::var_os("AGENT_BLOCK_KNL_PATH").map(PathBuf::from),
project_root,
)
}
fn resolve_knl_path(
override_path: Option<PathBuf>,
project_root: &Path,
) -> Result<PathBuf, String> {
if let Some(path) = override_path {
return Ok(path);
}
Ok(base_dir()?
.join("projects")
.join(project_slug(project_root))
.join("knl.sqlite"))
}
#[cfg(feature = "sqlite")]
pub fn is_memory_sql(path: &std::path::Path) -> bool {
path.as_os_str() == ":memory:"
}
#[cfg(feature = "sqlite")]
pub fn sql_busy_timeout() -> Duration {
let ms = std::env::var("AGENT_BLOCK_SQL_BUSY_TIMEOUT_MS")
.ok()
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(DEFAULT_SQL_BUSY_TIMEOUT_MS);
Duration::from_millis(ms)
}
#[cfg(feature = "sqlite")]
pub fn sql_journal_mode() -> String {
std::env::var("AGENT_BLOCK_SQL_JOURNAL_MODE")
.unwrap_or_else(|_| DEFAULT_SQL_JOURNAL_MODE.to_string())
}
#[cfg(feature = "sqlite")]
pub fn sql_query_timeout() -> Option<Duration> {
let ms = std::env::var("AGENT_BLOCK_SQL_QUERY_TIMEOUT_MS")
.ok()
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(DEFAULT_SQL_QUERY_TIMEOUT_MS);
if ms == 0 {
None
} else {
Some(Duration::from_millis(ms))
}
}
pub fn bus_capacity() -> usize {
match std::env::var("AGENT_BLOCK_BUS_CAPACITY") {
Ok(v) => v.parse::<usize>().unwrap_or_else(|e| {
tracing::warn!(
value = %v,
error = %e,
default = DEFAULT_BUS_CAPACITY,
"AGENT_BLOCK_BUS_CAPACITY parse failed, using default"
);
DEFAULT_BUS_CAPACITY
}),
Err(_) => DEFAULT_BUS_CAPACITY,
}
}
pub fn task_grace_ms() -> u64 {
match std::env::var("AGENT_BLOCK_TASK_GRACE_MS") {
Ok(v) => v.parse::<u64>().unwrap_or_else(|e| {
tracing::warn!(
value = %v,
error = %e,
default = DEFAULT_TASK_GRACE_MS,
"AGENT_BLOCK_TASK_GRACE_MS parse failed, using default"
);
DEFAULT_TASK_GRACE_MS
}),
Err(_) => DEFAULT_TASK_GRACE_MS,
}
}
#[cfg(test)]
mod knl_path_tests {
use super::*;
#[test]
fn a_slug_is_the_project_path_with_dashes() {
assert_eq!(
project_slug(Path::new("/home/u/projects/x")),
"-home-u-projects-x"
);
assert_eq!(project_slug(Path::new("/")), "-");
}
#[test]
fn two_projects_slug_apart() {
assert_ne!(
project_slug(Path::new("/home/u/a")),
project_slug(Path::new("/home/u/b"))
);
}
#[test]
fn the_default_is_per_project() {
let path = resolve_knl_path(None, Path::new("/home/u/projects/x")).expect("resolved");
assert!(
path.ends_with("projects/-home-u-projects-x/knl.sqlite"),
"{}",
path.display()
);
}
#[test]
fn an_override_wins() {
let path = resolve_knl_path(
Some(PathBuf::from("/tmp/elsewhere/knl.sqlite")),
Path::new("/home/u/projects/x"),
)
.expect("resolved");
assert_eq!(path, PathBuf::from("/tmp/elsewhere/knl.sqlite"));
}
}