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 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 #[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(), 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