1use anyhow::{bail, Context, Result};
2use cloudpub_common::constants::DEFAULT_HEARTBEAT_TIMEOUT_SECS;
3use cloudpub_common::{DOMAIN, PORT};
4use serde::{Deserialize, Serialize};
5use std::fmt::{Debug, Display, Formatter};
6use std::str::FromStr;
7
8use crate::options::ConfigOption;
9pub use cloudpub_common::config::{MaskedString, TransportConfig};
10use lazy_static::lazy_static;
11use machineid_rs::{Encryption, HWIDComponent, IdBuilder};
12use std::collections::HashMap;
13use std::fs::{self, create_dir_all, File};
14use std::io::Write;
15use std::path::PathBuf;
16use tracing::debug;
17use url::Url;
18use uuid::Uuid;
19
20#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
21pub enum Platform {
22 #[default]
23 X64,
24 X32,
25}
26
27#[derive(Clone)]
28pub struct EnvConfig {
29 pub home_1c: PathBuf,
30 #[cfg(target_os = "windows")]
31 pub redist: String,
32 pub httpd: String,
33 pub httpd_dir: String,
34}
35
36impl Default for EnvConfig {
37 fn default() -> Self {
38 #[cfg(target_pointer_width = "64")]
39 return ENV_CONFIG.get(&Platform::X64).unwrap().clone();
40 #[cfg(target_pointer_width = "32")]
41 return ENV_CONFIG.get(&Platform::X32).unwrap().clone();
42 }
43}
44
45lazy_static! {
46 pub static ref ENV_CONFIG: HashMap<Platform, EnvConfig> = {
47 let mut m = HashMap::new();
48 #[cfg(target_os = "windows")]
49 m.insert(
50 Platform::X64,
51 EnvConfig {
52 home_1c: PathBuf::from("C:\\Program Files\\1cv8\\"),
53 redist: "vc_redist.x64.exe".to_string(),
54 httpd: "httpd-2.4.61-240703-win64-VS17.zip".to_string(),
55 httpd_dir: "httpd-x64".to_string(),
56 },
57 );
58 #[cfg(target_os = "windows")]
59 m.insert(
60 Platform::X32,
61 EnvConfig {
62 home_1c: PathBuf::from("C:\\Program Files (x86)\\1cv8"),
63 redist: "vc_redist.x86.exe".to_string(),
64 httpd: "httpd-2.4.61-240703-win32-vs17.zip".to_string(),
65 httpd_dir: "httpd-x32".to_string(),
66 },
67 );
68 #[cfg(target_os = "linux")]
69 m.insert(
70 Platform::X64,
71 EnvConfig {
72 home_1c: PathBuf::from("/opt/1C"),
73 httpd: "httpd-2.4.63-linux.zip".to_string(),
74 httpd_dir: "httpd-x64".to_string(),
75 },
76 );
77 #[cfg(target_os = "macos")]
78 m.insert(
79 Platform::X64,
80 EnvConfig {
81 home_1c: PathBuf::from("/Applications/1C"),
82 httpd: "httpd-2.4.62-macos.zip".to_string(),
83 httpd_dir: "httpd-x64".to_string(),
84 },
85 );
86 m
87 };
88}
89
90impl Display for Platform {
91 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
92 match self {
93 Platform::X64 => write!(f, "x64"),
94 Platform::X32 => write!(f, "x32"),
95 }
96 }
97}
98
99impl std::str::FromStr for Platform {
100 type Err = anyhow::Error;
101
102 fn from_str(s: &str) -> Result<Self> {
103 match s {
104 "x64" => Ok(Platform::X64),
105 "x32" => Ok(Platform::X32),
106 _ => bail!("Invalid platform: {}", s),
107 }
108 }
109}
110
111#[derive(Debug, PartialEq, Eq, Clone, Default)]
112pub struct ClientOpts {
113 pub gui: bool,
114 pub credentials: Option<(String, String)>,
115 pub transient: bool,
116 pub secondary: bool,
117}
118
119#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
120pub struct ClientConfig {
121 #[serde(skip)]
123 config_path: PathBuf,
124 #[serde(skip)]
125 pub readonly: bool,
126 pub agent_id: String,
127 pub hwid: Option<String>,
128 pub server: Url,
129 pub token: Option<MaskedString>,
130 pub heartbeat_timeout: u64,
131 pub one_c_home: Option<String>,
132 pub one_c_platform: Option<Platform>,
133 pub one_c_publish_dir: Option<String>,
134 pub minecraft_server: Option<String>,
135 pub minecraft_java_opts: Option<String>,
136 pub minimize_to_tray_on_close: Option<bool>,
137 pub minimize_to_tray_on_start: Option<bool>,
138 pub transport: TransportConfig,
139}
140
141impl ClientConfig {
142 pub fn get_config_path(&self) -> &PathBuf {
143 &self.config_path
144 }
145
146 pub fn from_file(path: &PathBuf, readonly: bool) -> Result<Self> {
147 if !path.exists() {
148 let default_config = Self::default();
149 debug!("Creating default config at {:?}", default_config);
150 let s = toml::to_string_pretty(&default_config)
151 .context("Failed to serialize the default config")?;
152 fs::write(path, s).context("Failed to write the default config")?;
153 }
154
155 let s: String = fs::read_to_string(path)
156 .with_context(|| format!("Failed to read the config {:?}", path))?;
157 let mut cfg: Self = toml::from_str(&s).with_context(|| {
158 "Configuration is invalid. Please refer to the configuration specification."
159 })?;
160
161 cfg.config_path = path.clone();
162 cfg.readonly = readonly;
163 Ok(cfg)
164 }
165
166 pub fn get_config_dir(user_dir: bool) -> Result<PathBuf> {
167 let dir = if user_dir {
168 let mut dir = dirs::config_dir().context("Can't get config_dir")?;
169 dir.push("cloudpub");
170 dir
171 } else if cfg!(target_family = "unix") {
172 PathBuf::from("./etc/cloudpub")
173 } else {
174 PathBuf::from("C:\\Windows\\system32\\config\\systemprofile\\AppData\\Local\\cloudpub")
175 };
176 if !dir.exists() {
177 create_dir_all(&dir).context("Can't create config dir")?;
178 }
179 debug!("Config dir: {:?}", dir);
180 Ok(dir)
181 }
182
183 pub fn save(&self) -> Result<()> {
184 if self.readonly {
185 debug!("Skipping saving the config in readonly mode");
186 } else {
187 let s = toml::to_string_pretty(self).context("Failed to serialize the config")?;
188 let mut f = File::create(&self.config_path)?;
189 f.write_all(s.as_bytes())?;
190 f.sync_all()?;
191 }
192 Ok(())
193 }
194
195 pub fn load(cfg_name: &str, user_dir: bool, readonly: bool) -> Result<Self> {
196 let mut config_path = Self::get_config_dir(user_dir)?;
197 config_path.push(cfg_name);
198
199 Self::from_file(&config_path, readonly)
200 }
201
202 pub fn set(&mut self, key: &str, value: &str) -> Result<()> {
203 let option = ConfigOption::from_str(key)?;
204 self.set_option(option, value)
205 }
206
207 pub fn get(&self, key: &str) -> Result<String> {
208 let option = ConfigOption::from_str(key)?;
209 self.get_option(option)
210 }
211
212 pub fn get_all_options(&self) -> Vec<(String, String)> {
213 self.get_all_config_options()
214 }
215
216 pub fn validate(&self) -> Result<()> {
217 if self.token.is_none() {
218 bail!("{}", crate::t!("error-auth-missing"));
219 }
220 TransportConfig::validate(&self.transport, false)?;
221 Ok(())
222 }
223
224 pub fn get_hwid(&self) -> String {
225 self.hwid
226 .as_ref()
227 .unwrap_or(
228 &IdBuilder::new(Encryption::SHA256)
229 .add_component(HWIDComponent::OSName)
230 .add_component(HWIDComponent::SystemID)
231 .add_component(HWIDComponent::MachineName)
232 .add_component(HWIDComponent::CPUID)
233 .build("cloudpub")
234 .unwrap_or(self.agent_id.clone()),
235 )
236 .to_string()
237 }
238}
239
240impl Default for ClientConfig {
241 fn default() -> Self {
242 Self {
243 agent_id: Uuid::new_v4().to_string(),
244 hwid: None,
245 config_path: PathBuf::new(),
246 server: format!("https://{}:{}", DOMAIN, PORT).parse().unwrap(),
247 token: None,
248 heartbeat_timeout: DEFAULT_HEARTBEAT_TIMEOUT_SECS,
249 one_c_home: None,
250 one_c_platform: None,
251 one_c_publish_dir: None,
252 minecraft_server: None,
253 minecraft_java_opts: None,
254 minimize_to_tray_on_close: Some(true),
255 minimize_to_tray_on_start: Some(false),
256 transport: TransportConfig::default(),
257 readonly: false,
258 }
259 }
260}