flams_system/
settings.rs

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