146 lines
3.5 KiB
Rust
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)));
|
|
}
|
|
}
|