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#[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
20pub fn mkdir() -> Result<()> {
22 let data_path = data_path()?;
23 std::fs::create_dir_all(data_path)?;
24 Ok(())
25}
26
27pub 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
40pub fn daemon_file() -> Result<PathBuf> {
43 let mut dir = data_path()?;
44 dir.push("daemon-pid.yml");
45 Ok(dir)
46}
47
48pub 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
63pub fn daemon_log() -> Result<PathBuf> {
65 let mut dir = data_path()?;
66 dir.push("daemon.log");
67 Ok(dir)
68}
69
70pub fn pty_log() -> Result<PathBuf> {
72 let mut dir = data_path()?;
73 dir.push("pty.log");
74 Ok(dir)
75}
76
77pub 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
90pub 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
106pub fn global_config_file() -> Option<PathBuf> {
108 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}