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