ssh_utils_lib/config/
app_config.rs1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4use std::{fs, path::{Path, PathBuf}};
5
6use crate::helper::{get_file_path, CONFIG_FILE};
7
8#[derive(Serialize, Deserialize, Debug, Clone)]
9pub struct Server {
10 pub id: String,
11 pub name: String,
12 pub ip: String,
13 pub user: String,
14 pub shell: String,
15 pub port: u16,
16}
17
18impl Server {
19 pub fn new(name: String, ip: String, user: String, shell: String, port: u16) -> Self {
20 Self {
21 id: Uuid::new_v4().to_string(),
22 name,
23 ip,
24 user,
25 shell,
26 port,
27 }
28 }
29}
30
31#[derive(Serialize, Deserialize, Debug, Default)]
32pub struct Config {
33 pub servers: Vec<Server>,
34}
35
36impl Config {
37 pub fn save(&self) -> Result<()> {
41 let file_path = get_file_path(CONFIG_FILE)?;
42 let config_str = toml::to_string(self).context("Failed to serialize config to TOML.")?;
43
44 let path = PathBuf::from(&file_path);
46 if let Some(parent) = path.parent() {
47 if !parent.exists() {
48 fs::create_dir_all(parent).context(format!(
49 "Failed to create config directory at {:?}",
50 parent
51 ))?;
52 }
53 }
54
55 fs::write(&file_path, config_str)
57 .context(format!("Failed to write config to file at {:?}", file_path))?;
58
59 Ok(())
60 }
61
62 pub fn modify_server(&mut self, id: &str, new_server: Server) -> Result<()> {
66 if let Some(server) = self.servers.iter_mut().find(|server| server.id == id) {
67 server.name = new_server.name.clone();
68 server.ip = new_server.ip.clone();
69 server.user = new_server.user.clone();
70 server.shell = new_server.shell.clone();
71 server.port = new_server.port;
72 self.save()?;
73 } else {
74 return Err(anyhow::anyhow!("Server with id {} not found", id));
75 }
76 Ok(())
77 }
78
79 pub fn add_server(&mut self, new_server: Server) -> Result<()> {
83 self.servers.push(new_server);
84 self.save()?;
85 Ok(())
86 }
87
88 pub fn delete_server(&mut self, id: &str) -> Result<()> {
92 if let Some(pos) = self.servers.iter().position(|server| server.id == id) {
93 self.servers.remove(pos);
94 self.save()?;
95 } else {
96 return Err(anyhow::anyhow!("Server with id {} not found", id));
97 }
98 Ok(())
99 }
100}
101
102pub fn ensure_config_exists() -> Result<()> {
107 let mut config_dir: PathBuf = if cfg!(debug_assertions) {
108 ".".into() } else {
110 dirs::home_dir().context("Unable to reach user's home directory.")?
111 };
112 config_dir.push(".config/ssh-utils");
113
114 if !config_dir.exists() {
116 fs::create_dir_all(&config_dir).context(format!(
117 "Failed to create config directory at {:?}",
118 config_dir
119 ))?;
120 }
121
122 let config_file_path = config_dir.join("config.toml");
124 if !config_file_path.exists() {
125 fs::File::create(&config_file_path).context(format!(
126 "Failed to create config file at {:?}",
127 config_file_path
128 ))?;
129 }
130
131 Ok(())
132}
133
134pub fn read_config() -> Result<Config> {
139 let config_path = get_config_path()?;
140 read_config_from_path(&config_path)
141}
142
143fn get_config_path() -> Result<PathBuf> {
144 if cfg!(debug_assertions) {
145 Ok(".config/ssh-utils/config.toml".into())
146 } else {
147 let mut path = dirs::home_dir().context("Unable to reach user's home directory.")?;
148 path.push(".config/ssh-utils/config.toml");
149 Ok(path)
150 }
151}
152
153fn read_config_from_path<P: AsRef<Path>>(config_path: P) -> Result<Config> {
154 let config_str = fs::read_to_string(&config_path)
155 .with_context(|| format!("Unable to read ssh-utils' config file at {:?}", config_path.as_ref()))?;
156
157 if config_str.trim().is_empty() {
158 return Ok(Config::default());
159 }
160
161 let config: Config = toml::from_str(&config_str)
162 .context("Failed to parse ssh-utils' config file.")?;
163
164 Ok(config)
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use tempfile::TempDir;
171 use std::fs;
172
173 #[test]
174 fn test_read_config_empty_file() {
175 let temp_dir = TempDir::new().unwrap();
176 let config_path = temp_dir.path().join("config.toml");
177 fs::write(&config_path, "").unwrap();
178 let config = read_config_from_path(&config_path).unwrap();
179 assert!(config.servers.is_empty());
180 }
181
182 #[test]
183 fn test_read_config_with_servers() {
184 let temp_dir = TempDir::new().unwrap();
185 let config_path = temp_dir.path().join("config.toml");
186 let config_content = r#"
187 [[servers]]
188 id = "1"
189 name = "Server1"
190 ip = "192.168.1.1"
191 user = "user1"
192 shell = "/bin/bash"
193 port = 22
194
195 [[servers]]
196 id = "2"
197 name = "Server2"
198 ip = "192.168.1.2"
199 user = "user2"
200 shell = "/bin/zsh"
201 port = 2222
202 "#;
203 fs::write(&config_path, config_content).unwrap();
204
205 let config = read_config_from_path(&config_path).unwrap();
206 assert_eq!(config.servers.len(), 2);
207 assert_eq!(config.servers[0].name, "Server1");
208 assert_eq!(config.servers[1].port, 2222);
209 }
210}