flams_system/
settings.rs

1#![allow(clippy::ref_option)]
2
3use std::{
4    fmt::Debug,
5    path::{Path, PathBuf},
6    sync::atomic::AtomicU16,
7};
8
9use flams_utils::settings::GitlabSettings;
10pub use flams_utils::settings::{BuildQueueSettings, ServerSettings, SettingsSpec};
11
12static SETTINGS: std::sync::OnceLock<Settings> = std::sync::OnceLock::new();
13
14pub struct Settings {
15    pub mathhubs_is_default: bool,
16    pub debug: bool,
17    pub log_dir: Box<Path>,
18    pub port: AtomicU16,
19    pub ip: std::net::IpAddr,
20    pub admin_pwd: Option<Box<str>>,
21    pub database: Box<Path>,
22    pub rdf_database: Option<Box<Path>>,
23    pub stack_size: Option<u8>,
24    external_url: Option<Box<str>>,
25    temp_dir: parking_lot::RwLock<Option<tempfile::TempDir>>,
26    pub num_threads: u8,
27    pub gitlab_url: Option<url::Url>,
28    pub gitlab_token: Option<Box<str>>,
29    pub gitlab_app_id: Option<Box<str>>,
30    pub gitlab_app_secret: Option<Box<str>>,
31    pub gitlab_redirect_url: Option<Box<str>>,
32    pub lsp: bool,
33}
34impl Debug for Settings {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        f.write_str("Settings")
37    }
38}
39
40impl Settings {
41    #[inline]
42    pub fn mathhubs(&self) -> &'static [&'static Path] {
43        flams_math_archives::mathhub::mathhubs()
44    }
45    pub fn port(&self) -> u16 {
46        self.port.load(std::sync::atomic::Ordering::Relaxed)
47    }
48    #[allow(clippy::missing_panics_doc)]
49    pub fn initialize(settings: SettingsSpec) {
50        SETTINGS
51            .set(settings.into())
52            .expect("Error initializing settings");
53    }
54
55    #[allow(clippy::missing_panics_doc)]
56    pub fn get() -> &'static Self {
57        SETTINGS.get().expect("Settings not initialized")
58    }
59
60    #[inline]
61    pub fn external_url(&self) -> &str {
62        self.external_url.as_deref().unwrap_or_default()
63    }
64
65    /// #### Panics
66    pub fn temp_dir(&self) -> PathBuf {
67        self.temp_dir
68            .read()
69            .as_ref()
70            .expect("This should never happen!")
71            .path()
72            .to_path_buf()
73    }
74
75    #[allow(clippy::significant_drop_in_scrutinee)]
76    pub fn close(&self) {
77        if let Some(td) = self.temp_dir.write().take() {
78            let _ = td.close();
79        }
80    }
81
82    /// #### Panics
83    #[must_use]
84    pub fn as_spec(&self) -> SettingsSpec {
85        let port = self.port();
86        let spec = SettingsSpec {
87            mathhubs: flams_math_archives::mathhub::mathhubs()
88                .iter()
89                .map(|m| m.to_path_buf())
90                .collect(), // self.mathhubs.to_vec(),
91            debug: Some(self.debug),
92            log_dir: Some(self.log_dir.clone()),
93            temp_dir: Some(
94                self.temp_dir
95                    .read()
96                    .as_ref()
97                    .expect("This should never happen!")
98                    .path()
99                    .to_path_buf()
100                    .into_boxed_path(),
101            ),
102            database: Some(self.database.clone()),
103            rdf_database: self.rdf_database.clone(),
104            server: ServerSettings {
105                port,
106                ip: Some(self.ip),
107                external_url: self
108                    .external_url
109                    .as_ref()
110                    .map(ToString::to_string)
111                    .or_else(|| Some(format!("http://{}:{port}", self.ip))),
112                admin_pwd: self.admin_pwd.as_ref().map(ToString::to_string),
113            },
114            stack_size: self.stack_size,
115            buildqueue: BuildQueueSettings {
116                num_threads: Some(self.num_threads),
117            },
118            gitlab: GitlabSettings {
119                url: self.gitlab_url.clone(),
120                token: self.gitlab_token.clone(),
121                app_id: self.gitlab_app_id.clone(),
122                app_secret: self.gitlab_app_secret.clone(),
123                redirect_url: self.gitlab_redirect_url.clone(),
124            },
125            lsp: self.lsp,
126        };
127        spec
128    }
129}
130impl From<SettingsSpec> for Settings {
131    #[allow(clippy::cast_possible_truncation)]
132    fn from(spec: SettingsSpec) -> Self {
133        let mathhubs_is_default = if spec.mathhubs.is_empty() {
134            true
135        } else {
136            let mhs = spec.mathhubs;
137            let _ = flams_math_archives::mathhub::set_mathhubs(mhs);
138            flams_math_archives::mathhub::mathhubs()
139                == flams_math_archives::mathhub::default_mathhubs()
140        };
141        Self {
142            mathhubs_is_default,
143            debug: spec.debug.unwrap_or(cfg!(debug_assertions)),
144            log_dir: spec.log_dir.unwrap_or_else(|| {
145                CONFIG_DIR
146                    .as_ref()
147                    .expect("could not determine config directory")
148                    .join("log")
149                    .into_boxed_path()
150            }),
151            stack_size: spec.stack_size,
152            temp_dir: parking_lot::RwLock::new(Some(spec.temp_dir.map_or_else(
153                || tempfile::TempDir::new().expect("Could not create temp dir"),
154                |p| {
155                    let _ = std::fs::create_dir_all(&p);
156                    tempfile::Builder::new()
157                        .tempdir_in(p)
158                        .expect("Could not create temp dir")
159                },
160            ))),
161            external_url: spec.server.external_url.map(String::into_boxed_str),
162            port: AtomicU16::new(if spec.server.port == 0 {
163                8095
164            } else {
165                spec.server.port
166            }),
167            ip: spec
168                .server
169                .ip
170                .unwrap_or_else(|| "127.0.0.1".parse().unwrap_or_else(|_| unreachable!())),
171            admin_pwd: if spec.lsp {
172                None
173            } else {
174                spec.server.admin_pwd.map(String::into_boxed_str)
175            },
176            database: spec.database.unwrap_or_else(|| {
177                CONFIG_DIR
178                    .as_ref()
179                    .expect("could not determine config directory")
180                    .join("users.sqlite")
181                    .into_boxed_path()
182            }),
183            rdf_database: spec.rdf_database,
184            num_threads: spec.buildqueue.num_threads.unwrap_or_else(|| {
185                #[cfg(feature = "tokio")]
186                {
187                    (tokio::runtime::Handle::current().metrics().num_workers() / 2) as u8
188                }
189                #[cfg(not(feature = "tokio"))]
190                {
191                    1
192                }
193            }),
194            lsp: spec.lsp,
195            gitlab_token: spec.gitlab.token,
196            gitlab_url: spec.gitlab.url,
197            gitlab_app_id: spec.gitlab.app_id,
198            gitlab_app_secret: spec.gitlab.app_secret,
199            gitlab_redirect_url: spec.gitlab.redirect_url,
200        }
201    }
202}
203
204static CONFIG_DIR: std::sync::LazyLock<Option<Box<Path>>> = std::sync::LazyLock::new(|| {
205    simple_home_dir::home_dir().map(|d| d.join(".flams").into_boxed_path())
206});
207
208/*
209static EXE_DIR: std::sync::LazyLock<Option<Box<Path>>> = std::sync::LazyLock::new(|| {
210    std::env::current_exe()
211        .ok()
212        .and_then(|p| p.parent().map(Into::into))
213});
214 */