use crate::TransportError;
use tokio::process::Child;
use tracing::{debug, warn};
#[derive(Debug)]
pub struct Connection {
ssh_process: Option<Child>,
connected: bool,
}
impl Connection {
pub fn new(ssh_process: Option<Child>) -> Self {
let connected = ssh_process.is_some();
Self {
ssh_process,
connected,
}
}
pub fn is_connected(&self) -> bool {
self.connected
}
pub fn process_mut(&mut self) -> Option<&mut Child> {
self.ssh_process.as_mut()
}
pub fn take_process(&mut self) -> Option<Child> {
self.connected = false;
self.ssh_process.take()
}
pub async fn close(&mut self) -> Result<(), TransportError> {
if let Some(mut child) = self.ssh_process.take() {
debug!("Closing SSH connection");
if let Err(e) = child.kill().await {
warn!("Failed to kill SSH process: {}", e);
}
match child.wait().await {
Ok(status) => {
debug!("SSH process exited with status: {}", status);
}
Err(e) => {
warn!("Error waiting for SSH process: {}", e);
}
}
}
self.connected = false;
Ok(())
}
pub fn stdin(&mut self) -> Option<&mut tokio::process::ChildStdin> {
self.ssh_process.as_mut()?.stdin.as_mut()
}
pub fn stdout(&mut self) -> Option<&mut tokio::process::ChildStdout> {
self.ssh_process.as_mut()?.stdout.as_mut()
}
pub fn stderr(&mut self) -> Option<&mut tokio::process::ChildStderr> {
self.ssh_process.as_mut()?.stderr.as_mut()
}
}
impl Drop for Connection {
fn drop(&mut self) {
if let Some(mut child) = self.ssh_process.take() {
let _ = child.start_kill();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_connection_creation() {
let conn = Connection::new(None);
assert!(!conn.is_connected());
}
#[tokio::test]
async fn test_connection_close() {
let mut conn = Connection::new(None);
let result = conn.close().await;
assert!(result.is_ok());
assert!(!conn.is_connected());
}
}