1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! Launches `tab-daemon` and `tab-pty` processes.
//! The initial launch occurs in the `tab-cli`, using the currently running executible id.
//! `tab` exposes a hidden `tab --_launch [daemon|pty]` argument, which is used here to launch associated services.

use crate::{
    config::{is_running, load_daemon_file, DaemonConfig},
    env::is_raw_mode,
};
use lifeline::prelude::*;
use log::*;
use std::{
    process::Stdio,
    time::{Duration, Instant},
};
use tokio::{process::Command, select, signal::ctrl_c, time};

/// Launches a new daemon process (if it is not already running), and waits until it is ready for websocket connections.
pub async fn launch_daemon() -> anyhow::Result<DaemonConfig> {
    let exec = std::env::current_exe()?;
    let daemon_file = load_daemon_file()?;

    let running = daemon_file
        .as_ref()
        .map(|config| is_running(config))
        .unwrap_or(false);

    let start_wait = Instant::now();
    if !running {
        debug!("launching `tab-daemon` at {}", &exec.to_string_lossy());

        let mut child = Command::new(exec);

        child
            .args(&["--_launch", "daemon"])
            .stdin(Stdio::null())
            .stdout(Stdio::null())
            .kill_on_drop(false);

        if is_raw_mode() {
            child.stderr(Stdio::null());
        } else {
            child.stderr(Stdio::inherit());
        }

        crate::env::forward_env(&mut child);

        let _child = child.spawn()?;
    }

    let timeout_duration = Duration::from_secs(2);

    let mut index = 0usize;
    let daemon_file = loop {
        if let Some(daemon_file) = load_daemon_file()? {
            if is_running(&daemon_file) {
                break daemon_file;
            }
        }

        time::delay_for(Duration::from_millis(50)).await;
        if Instant::now().duration_since(start_wait) > timeout_duration {
            return Err(anyhow::Error::msg("timeout while waiting for tab daemon"));
        }

        if index == 1 {
            info!("waiting for daemon...");
        }

        index += 1;
    };

    Ok(daemon_file)
}

/// Launches a new PTY process, which will connect to the running daemon.
pub fn launch_pty() -> anyhow::Result<()> {
    let exec = std::env::current_exe()?;
    debug!("launching `tab-pty` at {}", &exec.to_string_lossy());

    let mut child = Command::new(exec);
    child
        .args(&["--_launch", "pty"])
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .kill_on_drop(false);

    if is_raw_mode() {
        child.stderr(Stdio::null());
    } else {
        child.stderr(Stdio::inherit());
    }

    crate::env::forward_env(&mut child);

    let _child = child.spawn()?;

    Ok(())
}

/// Waits for either a ctrl-c signal, or a message on the given channel.
///
/// Useful in main() functions.
pub async fn wait_for_shutdown<T>(mut receiver: impl Receiver<T>) {
    info!("Waiting for termination");

    loop {
        select! {
            _ = ctrl_c() => {
                break;
            },
            _ = receiver.recv() => {
                break;
            }
        }
    }

    info!("Complete.  Shutting down");
}