use dashmap::DashMap;
use shell_engine::shell::Shell;
use std::sync::Arc;
use tokio::sync::Mutex;
#[derive(Clone, Default)]
pub struct ShellRegistry {
inner: Arc<DashMap<String, Arc<Mutex<Shell>>>>,
}
impl ShellRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn get(&self, tag: &str) -> Result<Arc<Mutex<Shell>>, String> {
self.inner
.get(tag)
.map(|entry| entry.value().clone())
.ok_or_else(|| format!("Session '{tag}' does not exist"))
}
pub fn contains(&self, tag: &str) -> bool {
self.inner.contains_key(tag)
}
pub fn insert_new(&self, tag: String, shell: Shell) -> Result<(), String> {
match self.inner.entry(tag.clone()) {
dashmap::Entry::Occupied(_) => Err(format!("Session '{tag}' already exists")),
dashmap::Entry::Vacant(slot) => {
slot.insert(Arc::new(Mutex::new(shell)));
Ok(())
}
}
}
pub fn remove(&self, tag: &str) -> Option<Arc<Mutex<Shell>>> {
self.inner.remove(tag).map(|(_, v)| v)
}
pub fn tags(&self) -> Vec<String> {
self.inner.iter().map(|entry| entry.key().clone()).collect()
}
pub fn describe_all(&self) -> Vec<serde_json::Value> {
self.inner
.iter()
.map(|entry| {
let tag = entry.key().clone();
match entry.value().try_lock() {
Ok(guard) => {
let (is_pty, pty_size) = (guard.is_pty(), guard.pty_window_size());
serde_json::json!({
"tag": tag,
"shell_path": guard.shell_path,
"is_pty": is_pty,
"pty_size": pty_size,
"stdout_truncated_bytes": guard.output_truncated_bytes(),
"stderr_truncated_bytes": guard.error_truncated_bytes(),
})
}
Err(_) => serde_json::json!({ "tag": tag, "busy": true }),
}
})
.collect()
}
pub async fn close_all(&self) -> (Vec<String>, Vec<String>) {
let mut closed = Vec::new();
let mut errors = Vec::new();
for tag in self.tags() {
if let Some(shell) = self.remove(&tag) {
match shell.lock().await.close() {
Ok(_) => closed.push(tag),
Err(e) => errors.push(format!("{tag}: {e}")),
}
}
}
(closed, errors)
}
}