cloudpub_client/
config.rs

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    pub is_service: bool,
118}
119
120#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
121pub struct ClientConfig {
122    // This fields are not persistent
123    #[serde(skip)]
124    config_path: PathBuf,
125    #[serde(skip)]
126    pub readonly: bool,
127    pub agent_id: String,
128    pub hwid: Option<String>,
129    pub server: Url,
130    pub token: Option<MaskedString>,
131    pub heartbeat_timeout: u64,
132    pub one_c_home: Option<String>,
133    pub one_c_platform: Option<Platform>,
134    pub one_c_publish_dir: Option<String>,
135    pub minecraft_server: Option<String>,
136    pub minecraft_java_opts: Option<String>,
137    pub minimize_to_tray_on_close: Option<bool>,
138    pub minimize_to_tray_on_start: Option<bool>,
139    pub transport: TransportConfig,
140}
141
142impl ClientConfig {
143    pub fn get_config_path(&self) -> &PathBuf {
144        &self.config_path
145    }
146
147    pub fn from_file(path: &PathBuf, readonly: bool) -> Result<Self> {
148        if !path.exists() && !readonly {
149            let default_config = Self::default();
150            debug!("Creating default config at {:?}", default_config);
151            let s = toml::to_string_pretty(&default_config)
152                .context("Failed to serialize the default config")?;
153            fs::write(path, s).context("Failed to write the default config")?;
154        }
155
156        let s: String = fs::read_to_string(path)
157            .with_context(|| format!("Failed to read the config {:?}", path))?;
158        let mut cfg: Self = toml::from_str(&s).with_context(|| {
159            "Configuration is invalid. Please refer to the configuration specification."
160        })?;
161
162        cfg.config_path = path.clone();
163        cfg.readonly = readonly;
164        Ok(cfg)
165    }
166
167    pub fn get_config_dir(user_dir: bool) -> Result<PathBuf> {
168        let dir = if user_dir {
169            let mut dir = dirs::config_dir().context("Can't get config_dir")?;
170            dir.push("cloudpub");
171            dir
172        } else if cfg!(target_family = "unix") {
173            PathBuf::from("./etc/cloudpub")
174        } else {
175            PathBuf::from("C:\\Windows\\system32\\config\\systemprofile\\AppData\\Local\\cloudpub")
176        };
177        if !dir.exists() {
178            create_dir_all(&dir).context("Can't create config dir")?;
179        }
180        debug!("Config dir: {:?}", dir);
181        Ok(dir)
182    }
183
184    pub fn save(&self) -> Result<()> {
185        if self.readonly {
186            debug!("Skipping saving the config in readonly mode");
187        } else {
188            let s = toml::to_string_pretty(self).context("Failed to serialize the config")?;
189            let mut f = File::create(&self.config_path)?;
190            f.write_all(s.as_bytes())?;
191            f.sync_all()?;
192        }
193        Ok(())
194    }
195
196    pub fn load(cfg_name: &str, user_dir: bool, readonly: bool) -> Result<Self> {
197        let mut config_path = Self::get_config_dir(user_dir)?;
198        config_path.push(cfg_name);
199
200        Self::from_file(&config_path, readonly)
201    }
202
203    pub fn set(&mut self, key: &str, value: &str) -> Result<()> {
204        let option = ConfigOption::from_str(key)?;
205        self.set_option(option, value)
206    }
207
208    pub fn get(&self, key: &str) -> Result<String> {
209        let option = ConfigOption::from_str(key)?;
210        self.get_option(option)
211    }
212
213    pub fn get_all_options(&self) -> Vec<(String, String)> {
214        self.get_all_config_options()
215    }
216
217    pub fn validate(&self) -> Result<()> {
218        if self.token.is_none() {
219            bail!("{}", crate::t!("error-auth-missing"));
220        }
221        TransportConfig::validate(&self.transport, false)?;
222        Ok(())
223    }
224
225    pub fn get_hwid(&self) -> String {
226        self.hwid
227            .as_ref()
228            .unwrap_or(
229                &IdBuilder::new(Encryption::SHA256)
230                    .add_component(HWIDComponent::OSName)
231                    .add_component(HWIDComponent::SystemID)
232                    .add_component(HWIDComponent::MachineName)
233                    .add_component(HWIDComponent::CPUID)
234                    .build("cloudpub")
235                    .unwrap_or(self.agent_id.clone()),
236            )
237            .to_string()
238    }
239}
240
241impl Default for ClientConfig {
242    fn default() -> Self {
243        Self {
244            agent_id: Uuid::new_v4().to_string(),
245            hwid: None,
246            config_path: PathBuf::new(),
247            server: format!("https://{}:{}", DOMAIN, PORT).parse().unwrap(),
248            token: None,
249            heartbeat_timeout: DEFAULT_HEARTBEAT_TIMEOUT_SECS,
250            one_c_home: None,
251            one_c_platform: None,
252            one_c_publish_dir: None,
253            minecraft_server: None,
254            minecraft_java_opts: None,
255            minimize_to_tray_on_close: Some(true),
256            minimize_to_tray_on_start: Some(false),
257            transport: TransportConfig::default(),
258            readonly: false,
259        }
260    }
261}