use crate::connection::AimxClient;
use crate::error::{ClientError, ClientResult};
use crate::protocol::WelcomeMessage;
use std::path::PathBuf;
use std::time::Duration;
const SOCKET_SEARCH_DIRS: &[&str] = &["/tmp", "/var/run/aimdb"];
#[derive(Debug, Clone)]
pub struct InstanceInfo {
pub socket_path: PathBuf,
pub server_version: String,
pub protocol_version: String,
pub permissions: Vec<String>,
pub writable_records: Vec<String>,
pub max_subscriptions: Option<usize>,
pub authenticated: bool,
}
impl From<(PathBuf, WelcomeMessage)> for InstanceInfo {
fn from((socket_path, welcome): (PathBuf, WelcomeMessage)) -> Self {
Self {
socket_path,
server_version: welcome.server,
protocol_version: welcome.version,
permissions: welcome.permissions,
writable_records: welcome.writable_records,
max_subscriptions: welcome.max_subscriptions,
authenticated: welcome.authenticated.unwrap_or(false),
}
}
}
pub async fn discover_instances() -> ClientResult<Vec<InstanceInfo>> {
let mut instances = Vec::new();
for dir_path in SOCKET_SEARCH_DIRS {
if let Ok(entries) = tokio::fs::read_dir(dir_path).await {
instances.extend(scan_directory(entries).await);
}
}
if instances.is_empty() {
return Err(ClientError::NoInstancesFound);
}
Ok(instances)
}
async fn scan_directory(mut entries: tokio::fs::ReadDir) -> Vec<InstanceInfo> {
let mut instances = Vec::new();
while let Ok(Some(entry)) = entries.next_entry().await {
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("sock") {
if let Ok(info) = probe_instance(&path).await {
instances.push(info);
}
}
}
instances
}
async fn probe_instance(socket_path: &PathBuf) -> ClientResult<InstanceInfo> {
let connect_timeout = Duration::from_millis(500);
let client = tokio::time::timeout(connect_timeout, AimxClient::connect(socket_path))
.await
.map_err(|_| {
ClientError::connection_failed(
socket_path.display().to_string(),
"timeout during discovery probe",
)
})??;
let welcome = client.server_info().clone();
Ok(InstanceInfo::from((socket_path.clone(), welcome)))
}
pub async fn find_instance(socket_hint: Option<&str>) -> ClientResult<InstanceInfo> {
if let Some(socket_path) = socket_hint {
let path = PathBuf::from(socket_path);
if path.exists() {
return probe_instance(&path).await;
} else {
return Err(ClientError::connection_failed(
socket_path.to_string(),
"socket file does not exist",
));
}
}
let instances = discover_instances().await?;
instances
.into_iter()
.next()
.ok_or(ClientError::NoInstancesFound)
}