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