Skip to main content

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