shepherd-launcher/crates/shepherd-host-api/src/traits.rs

146 lines
3.5 KiB
Rust

//! Host adapter traits
use async_trait::async_trait;
use shepherd_api::EntryKind;
use shepherd_util::SessionId;
use std::time::Duration;
use thiserror::Error;
use tokio::sync::mpsc;
use crate::{ExitStatus, HostCapabilities, HostSessionHandle};
/// Errors from host adapter operations
#[derive(Debug, Error)]
pub enum HostError {
#[error("Spawn failed: {0}")]
SpawnFailed(String),
#[error("Stop failed: {0}")]
StopFailed(String),
#[error("Unsupported entry kind")]
UnsupportedKind,
#[error("Session not found")]
SessionNotFound,
#[error("Permission denied: {0}")]
PermissionDenied(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Internal error: {0}")]
Internal(String),
}
pub type HostResult<T> = Result<T, HostError>;
/// Stop mode for session termination
#[derive(Debug, Clone, Copy)]
pub enum StopMode {
/// Try graceful stop with timeout, then force
Graceful { timeout: Duration },
/// Force immediate termination
Force,
}
impl Default for StopMode {
fn default() -> Self {
Self::Graceful {
timeout: Duration::from_secs(5),
}
}
}
/// Options for spawning a session
#[derive(Debug, Clone, Default)]
pub struct SpawnOptions {
/// Capture stdout to log file
pub capture_stdout: bool,
/// Capture stderr to log file
pub capture_stderr: bool,
/// Log file path (if capturing)
pub log_path: Option<std::path::PathBuf>,
/// Request fullscreen (if supported)
pub fullscreen: bool,
/// Request foreground focus (if supported)
pub foreground: bool,
}
/// Events from the host adapter
#[derive(Debug, Clone)]
pub enum HostEvent {
/// Process/session has exited
Exited {
handle: HostSessionHandle,
status: ExitStatus,
},
/// Window is ready (for UI notification)
WindowReady {
handle: HostSessionHandle,
},
/// Spawn failed after handle was created
SpawnFailed {
session_id: SessionId,
error: String,
},
}
/// Host adapter trait - implemented by platform-specific adapters
#[async_trait]
pub trait HostAdapter: Send + Sync {
/// Get the capabilities of this host adapter
fn capabilities(&self) -> &HostCapabilities;
/// Spawn a new session
async fn spawn(
&self,
session_id: SessionId,
entry_kind: &EntryKind,
options: SpawnOptions,
) -> HostResult<HostSessionHandle>;
/// Stop a running session
async fn stop(&self, handle: &HostSessionHandle, mode: StopMode) -> HostResult<()>;
/// Subscribe to host events
fn subscribe(&self) -> mpsc::UnboundedReceiver<HostEvent>;
/// Optional: set foreground focus (if supported)
async fn set_foreground(&self, _handle: &HostSessionHandle) -> HostResult<()> {
Err(HostError::Internal("Not supported".into()))
}
/// Optional: set fullscreen mode (if supported)
async fn set_fullscreen(&self, _handle: &HostSessionHandle) -> HostResult<()> {
Err(HostError::Internal("Not supported".into()))
}
/// Optional: ensure the shell/launcher is visible
async fn ensure_shell_visible(&self) -> HostResult<()> {
Ok(())
}
/// Optional: check if the host adapter is healthy
fn is_healthy(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stop_mode_default() {
let mode = StopMode::default();
assert!(matches!(mode, StopMode::Graceful { timeout } if timeout == Duration::from_secs(5)));
}
}