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