scosh-core 0.1.0

Portable session state and terminal event core for scosh
Documentation
//! Deterministic host lifecycle transitions.

use crate::{errors::SdkError, events::SessionStatus};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LifecycleEvent {
    Connected,
    NetworkLost,
    Backgrounded,
    Foregrounded,
    RemoteExit,
    Cancelled,
    Failed,
    Closed,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Lifecycle {
    status: SessionStatus,
    input_generation: u64,
    transport_open: bool,
}

impl Default for Lifecycle {
    fn default() -> Self {
        Self::new()
    }
}

impl Lifecycle {
    pub const fn new() -> Self {
        Self {
            status: SessionStatus::Connecting,
            input_generation: 0,
            transport_open: false,
        }
    }

    pub const fn status(self) -> SessionStatus {
        self.status
    }

    pub const fn input_generation(self) -> u64 {
        self.input_generation
    }

    pub const fn transport_open(self) -> bool {
        self.transport_open
    }

    pub fn transition(&mut self, event: LifecycleEvent) -> Result<SessionStatus, SdkError> {
        let next = match (self.status, event) {
            (SessionStatus::Connecting, LifecycleEvent::Connected) => {
                self.transport_open = true;
                SessionStatus::Live
            }
            (SessionStatus::Live, LifecycleEvent::NetworkLost)
            | (SessionStatus::Suspended, LifecycleEvent::Foregrounded) => {
                self.transport_open = false;
                SessionStatus::Recovering
            }
            (SessionStatus::Live, LifecycleEvent::Backgrounded) => {
                self.invalidate_input();
                self.transport_open = false;
                SessionStatus::Suspended
            }
            (SessionStatus::Recovering, LifecycleEvent::Connected) => {
                self.transport_open = true;
                SessionStatus::Live
            }
            (_, LifecycleEvent::RemoteExit | LifecycleEvent::Closed) => {
                self.transport_open = false;
                SessionStatus::Closed
            }
            (_, LifecycleEvent::Cancelled | LifecycleEvent::Failed) => {
                self.transport_open = false;
                SessionStatus::Failed
            }
            (SessionStatus::Recovering, LifecycleEvent::Backgrounded) => {
                self.invalidate_input();
                self.transport_open = false;
                SessionStatus::Suspended
            }
            (status, _) => {
                return Err(if status == SessionStatus::Closed {
                    SdkError::SessionClosed
                } else {
                    SdkError::TransportUnavailable
                });
            }
        };
        self.status = next;
        Ok(next)
    }

    fn invalidate_input(&mut self) {
        self.input_generation = self.input_generation.saturating_add(1);
    }
}