rmux-server 0.1.1

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use rmux_proto::{RmuxError, SessionName};

use super::{session_not_found, HandlerState};

impl HandlerState {
    pub(in crate::pane_terminals) fn runtime_session_name(
        &self,
        session_name: &SessionName,
    ) -> SessionName {
        self.sessions
            .runtime_owner(session_name)
            .unwrap_or_else(|| session_name.clone())
    }

    pub(crate) fn synchronize_session_group_from(
        &mut self,
        source_session_name: &SessionName,
    ) -> Result<Vec<SessionName>, RmuxError> {
        let source_session = self
            .sessions
            .session(source_session_name)
            .cloned()
            .ok_or_else(|| session_not_found(source_session_name))?;
        let group_members = self.sessions.session_group_members(source_session_name);
        if group_members.len() <= 1 {
            return Ok(group_members);
        }

        let mut synchronized = Vec::with_capacity(group_members.len());
        for member_name in group_members {
            if member_name == *source_session_name {
                synchronized.push(member_name);
                continue;
            }

            let member = self
                .sessions
                .session_mut(&member_name)
                .ok_or_else(|| session_not_found(&member_name))?;
            member.synchronize_group_from(&source_session);
            synchronized.push(member_name);
        }

        Ok(synchronized)
    }

    pub(crate) fn rename_session(
        &mut self,
        session_name: &SessionName,
        new_name: &SessionName,
    ) -> Result<(), RmuxError> {
        let mut completed = Vec::new();
        let runtime_session_name = self.runtime_session_name(session_name);

        self.sessions
            .rename_session(session_name, new_name.clone())?;
        completed.push(RenameSessionStep::Sessions);

        if let Err(error) = self.options.rename_session(session_name, new_name.clone()) {
            self.rollback_session_rename(&completed, session_name, new_name, &error)?;
            return Err(error);
        }
        completed.push(RenameSessionStep::Options);

        if let Err(error) = self
            .environment
            .rename_session(session_name, new_name.clone())
        {
            self.rollback_session_rename(&completed, session_name, new_name, &error)?;
            return Err(error);
        }
        completed.push(RenameSessionStep::Environment);

        if let Err(error) = self.hooks.rename_session(session_name, new_name.clone()) {
            self.rollback_session_rename(&completed, session_name, new_name, &error)?;
            return Err(error);
        }
        completed.push(RenameSessionStep::Hooks);

        if runtime_session_name == *session_name {
            if let Err(error) = self.terminals.rename_session(session_name, new_name) {
                self.rollback_session_rename(&completed, session_name, new_name, &error)?;
                return Err(error);
            }
            completed.push(RenameSessionStep::Terminals);
        }

        if runtime_session_name == *session_name {
            if let Err(error) = self.rename_runtime_session_state(session_name, new_name) {
                self.rollback_session_rename(&completed, session_name, new_name, &error)?;
                return Err(error);
            }
        }

        Ok(())
    }

    pub(crate) fn remove_session_terminals(
        &mut self,
        session_name: &SessionName,
        current_runtime_owner: Option<&SessionName>,
        next_runtime_owner: Option<&SessionName>,
    ) -> Result<bool, RmuxError> {
        let Some(current_runtime_owner) = current_runtime_owner else {
            return Ok(false);
        };
        if current_runtime_owner != session_name {
            return Ok(true);
        }

        if let Some(next_runtime_owner) = next_runtime_owner {
            self.terminals
                .rename_session(session_name, next_runtime_owner)?;
            self.rename_runtime_session_state(session_name, next_runtime_owner)?;
            self.sync_pane_lifecycle_dimensions_for_session(next_runtime_owner);
            return Ok(true);
        }

        if self.session_has_marked_pane(session_name) {
            self.clear_marked_pane();
        }

        for pipe in self.remove_session_pipes(session_name).into_values() {
            pipe.stop();
        }
        self.remove_session_pane_outputs(session_name);
        let _ = self.dead_panes.remove(session_name);
        let _ = self.attached_submitted_rows.remove(session_name);
        self.auto_named_windows
            .retain(|(tracked_session, _)| tracked_session != session_name);
        let mut removed_terminals = self.terminals.remove_session(session_name);
        if let Some(panes) = removed_terminals.as_mut() {
            for terminal in panes.values_mut() {
                terminal.terminate_with_bounded_grace();
            }
            for pane_id in panes.keys() {
                self.remove_pane_lifecycle(*pane_id);
            }
        }
        Ok(removed_terminals.is_some())
    }

    fn rename_runtime_session_state(
        &mut self,
        session_name: &SessionName,
        new_name: &SessionName,
    ) -> Result<(), RmuxError> {
        if !self.transcripts.contains_key(session_name) {
            return Err(RmuxError::Server(format!(
                "missing pane transcripts for session {session_name}"
            )));
        }
        if self.transcripts.contains_key(new_name) {
            return Err(RmuxError::Server(format!(
                "pane transcripts already exist for session {new_name}"
            )));
        }
        if !self.pane_outputs.contains_key(session_name) {
            return Err(RmuxError::Server(format!(
                "missing pane output channels for session {session_name}"
            )));
        }
        if self.pane_outputs.contains_key(new_name) {
            return Err(RmuxError::Server(format!(
                "pane output channels already exist for session {new_name}"
            )));
        }

        let mut transcripts = std::mem::take(&mut self.transcripts);
        let mut pane_outputs = std::mem::take(&mut self.pane_outputs);
        let mut dead_panes = std::mem::take(&mut self.dead_panes);
        let mut attached_submitted_rows = std::mem::take(&mut self.attached_submitted_rows);

        let session_transcripts = transcripts
            .remove(session_name)
            .expect("prevalidated pane transcripts must exist");
        let session_outputs = pane_outputs
            .remove(session_name)
            .expect("prevalidated pane outputs must exist");
        let session_dead_panes = dead_panes.remove(session_name).unwrap_or_default();
        let session_attached_rows = attached_submitted_rows
            .remove(session_name)
            .unwrap_or_default();

        debug_assert!(transcripts
            .insert(new_name.clone(), session_transcripts)
            .is_none());
        debug_assert!(pane_outputs
            .insert(new_name.clone(), session_outputs)
            .is_none());
        if !session_dead_panes.is_empty() {
            debug_assert!(dead_panes
                .insert(new_name.clone(), session_dead_panes)
                .is_none());
        }
        if !session_attached_rows.is_empty() {
            debug_assert!(attached_submitted_rows
                .insert(new_name.clone(), session_attached_rows)
                .is_none());
        }
        let auto_named_windows = std::mem::take(&mut self.auto_named_windows)
            .into_iter()
            .map(|(tracked_session, window_index)| {
                if tracked_session == *session_name {
                    (new_name.clone(), window_index)
                } else {
                    (tracked_session, window_index)
                }
            })
            .collect();

        self.transcripts = transcripts;
        self.pane_outputs = pane_outputs;
        self.dead_panes = dead_panes;
        self.attached_submitted_rows = attached_submitted_rows;
        self.auto_named_windows = auto_named_windows;
        self.pipes.rename_session(session_name, new_name)?;
        Ok(())
    }

    fn rollback_session_rename(
        &mut self,
        completed: &[RenameSessionStep],
        session_name: &SessionName,
        new_name: &SessionName,
        source_error: &RmuxError,
    ) -> Result<(), RmuxError> {
        for step in completed.iter().rev().copied() {
            let rollback = match step {
                RenameSessionStep::Sessions => {
                    self.sessions.rename_session(new_name, session_name.clone())
                }
                RenameSessionStep::Options => {
                    self.options.rename_session(new_name, session_name.clone())
                }
                RenameSessionStep::Environment => self
                    .environment
                    .rename_session(new_name, session_name.clone()),
                RenameSessionStep::Hooks => {
                    self.hooks.rename_session(new_name, session_name.clone())
                }
                RenameSessionStep::Terminals => {
                    self.terminals.rename_session(new_name, session_name)
                }
            };

            if let Err(rollback_error) = rollback {
                return Err(RmuxError::Server(format!(
                    "failed to roll back session rename from {session_name} to {new_name} after {source_error}: {rollback_error}"
                )));
            }
        }

        Ok(())
    }
}

#[derive(Debug, Clone, Copy)]
enum RenameSessionStep {
    Sessions,
    Options,
    Environment,
    Hooks,
    Terminals,
}