tab_api/
config.rs

1use anyhow::Result;
2use lifeline::impl_storage_clone;
3use serde::Deserialize;
4use serde::Serialize;
5use std::{env, fs::File, io::BufReader, path::PathBuf};
6use sysinfo::{ProcessExt, RefreshKind, SystemExt};
7
8/// Config created for each daemon process
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct DaemonConfig {
11    pub pid: i32,
12    pub port: u16,
13    pub executable: Option<String>,
14    pub tab_version: Option<String>,
15    pub auth_token: String,
16}
17
18impl_storage_clone!(DaemonConfig);
19
20/// Creates the data path.
21pub fn mkdir() -> Result<()> {
22    let data_path = data_path()?;
23    std::fs::create_dir_all(data_path)?;
24    Ok(())
25}
26
27/// The full path to tab's data directory, which can be used to store state for the user.
28pub fn data_path() -> Result<PathBuf> {
29    if let Ok(var) = env::var("TAB_RUNTIME_DIR") {
30        return Ok(PathBuf::from(var));
31    }
32
33    let mut dir = dirs::data_dir().ok_or_else(|| anyhow::Error::msg("tab data dir not found"))?;
34
35    dir.push("tab");
36
37    Ok(dir)
38}
39
40/// The full path to the daemon's pidfile, used to identify the running process, and the available websocket port.
41/// Also stores an auth token that is required (in the Authorization header) to connect to the daemon.
42pub fn daemon_file() -> Result<PathBuf> {
43    let mut dir = data_path()?;
44    dir.push("daemon-pid.yml");
45    Ok(dir)
46}
47
48/// Determines if there is an active daemon, by checking the pidfile and the active system processes.
49pub fn is_running(config: &DaemonConfig) -> bool {
50    let mut system = sysinfo::System::new_with_specifics(RefreshKind::new());
51    system.refresh_process(config.pid);
52
53    match system.get_process(config.pid) {
54        Some(proc) => {
55            let command = proc.cmd();
56
57            command.contains(&"--_launch".to_string()) && command.contains(&"daemon".to_string())
58        }
59        None => false,
60    }
61}
62
63/// Returns the path to the daemon's logfile.
64pub fn daemon_log() -> Result<PathBuf> {
65    let mut dir = data_path()?;
66    dir.push("daemon.log");
67    Ok(dir)
68}
69
70/// Returns the path to the pty's logfile.
71pub fn pty_log() -> Result<PathBuf> {
72    let mut dir = data_path()?;
73    dir.push("pty.log");
74    Ok(dir)
75}
76
77/// Returns the path to a unique logfile fro the given shell process, and tab name.
78pub fn history_path(shell: &str, name: &str) -> Result<PathBuf> {
79    let mut path = data_path()?;
80    path.push("history");
81
82    let name = name.replace("/", "_");
83
84    let filename = format!("history-{}-{}.txt", shell, name);
85    path.push(filename);
86
87    Ok(path)
88}
89
90/// Loads & deserializes the `DaemonConfig` from the daemon pidfile.
91pub fn load_daemon_file() -> anyhow::Result<Option<DaemonConfig>> {
92    let path = daemon_file()?;
93
94    if !path.is_file() {
95        log::trace!("File {:?} does not exist", path.as_path());
96        return Ok(None);
97    }
98
99    let file = File::open(path)?;
100    let reader = BufReader::new(file);
101    let config = serde_yaml::from_reader(reader)?;
102
103    Ok(Some(config))
104}
105
106/// The full path to the global configuration file
107pub fn global_config_file() -> Option<PathBuf> {
108    // Use $TAB_CONFIG as the config file if available
109    if let Ok(path) = env::var("TAB_CONFIG") {
110        return Some(PathBuf::from(path));
111    }
112
113    if let Some(mut home_path) = dirs::home_dir() {
114        home_path.push(".config");
115        home_path.push("tab.yml");
116
117        if home_path.exists() {
118            return Some(home_path);
119        }
120    }
121
122    if let Some(mut config_path) = dirs::config_dir() {
123        config_path.push("tab.yml");
124
125        if config_path.exists() {
126            return Some(config_path);
127        }
128    }
129
130    None
131}
132
133#[cfg(test)]
134mod tests {
135    use super::{daemon_file, data_path};
136
137    #[test]
138    fn data_path_matches() {
139        let mut expected = dirs::data_dir().expect("home dir required");
140        expected.push("tab");
141
142        let path = data_path();
143        assert!(path.is_ok());
144        assert_eq!(expected, path.unwrap());
145    }
146
147    #[test]
148    fn daemonfile_path_matches() {
149        let mut expected = dirs::data_dir().expect("home dir required");
150        expected.push("tab");
151        expected.push("daemon-pid.yml");
152
153        let path = daemon_file();
154        assert!(path.is_ok());
155        assert_eq!(expected, path.unwrap());
156    }
157}