use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use harn_vm::VmValue;
use super::builtins::SharedIndex;
use super::state::IndexState;
use crate::error::HostlibError;
use crate::tools::args::{build_dict, dict_arg, optional_bool, optional_string_list, str_value};
pub type ReadonlyRoots = Arc<Mutex<Vec<IndexState>>>;
pub(super) const BUILTIN_ADD_READONLY_ROOTS: &str = "hostlib_code_index_add_readonly_roots";
pub(super) fn run_add_readonly_roots(
readonly: &ReadonlyRoots,
args: &[VmValue],
) -> Result<VmValue, HostlibError> {
let raw = dict_arg(BUILTIN_ADD_READONLY_ROOTS, args)?;
let dict = raw.as_ref();
let roots = optional_string_list(BUILTIN_ADD_READONLY_ROOTS, dict, "roots")?;
let replace = optional_bool(BUILTIN_ADD_READONLY_ROOTS, dict, "replace", false)?;
let mut guard = readonly.lock().expect("readonly roots mutex poisoned");
if replace {
guard.clear();
}
let mut added: Vec<VmValue> = Vec::with_capacity(roots.len());
let mut total_files: usize = 0;
for raw_root in roots {
let root = PathBuf::from(&raw_root);
if !root.exists() {
return Err(HostlibError::InvalidParameter {
builtin: BUILTIN_ADD_READONLY_ROOTS,
param: "roots",
message: format!("path `{}` does not exist", root.display()),
});
}
if !root.is_dir() {
return Err(HostlibError::InvalidParameter {
builtin: BUILTIN_ADD_READONLY_ROOTS,
param: "roots",
message: format!("path `{}` is not a directory", root.display()),
});
}
let (state, outcome) = IndexState::build_from_root(&root);
let files_indexed = outcome.files_indexed as i64;
total_files += state.files.len();
let canonical = state.root.clone();
if let Some(slot) = guard.iter_mut().find(|s| s.root == canonical) {
*slot = state;
} else {
guard.push(state);
}
added.push(build_dict([
("root", str_value(canonical.to_string_lossy().as_ref())),
("files_indexed", VmValue::Int(files_indexed)),
]));
}
Ok(build_dict([
("roots", VmValue::List(Arc::new(added))),
("readonly_root_count", VmValue::Int(guard.len() as i64)),
("readonly_files_indexed", VmValue::Int(total_files as i64)),
]))
}
pub(super) fn resolve_read_path(
primary: &SharedIndex,
readonly: &ReadonlyRoots,
path: &str,
) -> Option<PathBuf> {
let primary_resolved = {
let guard = primary.lock().expect("code_index mutex poisoned");
guard.as_ref().and_then(|state| state.absolute_path(path))
};
if let Some(abs) = primary_resolved.as_ref().filter(|p| p.exists()) {
return Some(abs.clone());
}
{
let guard = readonly.lock().expect("readonly roots mutex poisoned");
if let Some(abs) = guard
.iter()
.find_map(|state| state.absolute_path(path).filter(|p| p.exists()))
{
return Some(abs);
}
}
primary_resolved
}
pub(super) fn query_readonly_hits(
readonly: &ReadonlyRoots,
needle: &str,
case_sensitive: bool,
) -> Vec<super::builtins::Hit> {
let guard = readonly.lock().expect("readonly roots mutex poisoned");
let mut hits: Vec<super::builtins::Hit> = Vec::new();
for state in guard.iter() {
super::builtins::collect_hits_into(state, needle, case_sensitive, &mut hits);
}
hits
}