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}
118
119#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
120pub struct ClientConfig {
121    // This fields are not persistent
122    #[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}